Restructure into version-neutral core + V4 tree for upcoming v5 support #2

Merged
bjoern merged 2 commits from feat/v5-prep-version-trees into main 2026-07-08 13:55:34 +02:00
Member

Summary

Restructures the Dart project to mirror the C# NovelAI.ImageGen v5-prep restructure (commit d0e216a). This prepares for V5 support by splitting version-specific code into a V4 tree, extracting shared infrastructure, and slimming the client.

dart analyze: 0 issues. dart test: 104/104 passing.

Changes

1. Extracted shared HTTP transport

Created lib/src/internal/novelai_http_transport.dart:

  • NovelAIHttpTransport wraps Dio, exposes postAsync/getAsync returning Result<Uint8List>
  • Centralizes parseErrorResponse, getStatusDescription, and all DioException handling
  • Version-neutral — knows nothing about request payload shapes

2. Extracted V4ApiRequestBuilder

Created lib/src/internal/v4/v4_api_request_builder.dart:

  • V4ApiRequestBuilder.build(request, seed) — moved from NovelAIClient._buildApiRequest
  • All four _add*Data helpers (preciseReference, vibeTransfer, img2Img, inpaint)
  • Deterministic — seed passed in by the caller

3. Slimmed client

NovelAIClient now orchestrates: validation → V4ApiRequestBuilder.build()NovelAIHttpTransport.postAsync(). Removed ~250 lines of inline HTTP/error/payload code.

4. V4 directory tree

Moved 16 source files + 3 test files into v4/ subtrees:

  • lib/src/models/v4/ — character, position, request models, character_gender
  • lib/src/models/internal/v4/ — director refs, api request, params, v4 prompt/caption/character_prompt
  • lib/src/internal/v4/ — v4_prompt_builder, v4_api_request_builder

Version-neutral files stayed in place: tag, generated_image, result, vibe_embedding, enums (model, sampler, noise_schedule, reference_type), image_scaler, tag_serializer, zip_response_extractor.

What this enables

When V5 ships, adding support is now:

  1. Create lib/src/internal/v5/v5_api_request_builder.dart
  2. Create lib/src/models/v5/ with V5-specific request types
  3. Client switches between builders based on the model version

No changes to the transport, enums, or version-neutral models needed.

## Summary Restructures the Dart project to mirror the C# NovelAI.ImageGen v5-prep restructure (commit `d0e216a`). This prepares for V5 support by splitting version-specific code into a V4 tree, extracting shared infrastructure, and slimming the client. **`dart analyze`: 0 issues. `dart test`: 104/104 passing.** ## Changes ### 1. Extracted shared HTTP transport Created `lib/src/internal/novelai_http_transport.dart`: - `NovelAIHttpTransport` wraps Dio, exposes `postAsync`/`getAsync` returning `Result<Uint8List>` - Centralizes `parseErrorResponse`, `getStatusDescription`, and all `DioException` handling - Version-neutral — knows nothing about request payload shapes ### 2. Extracted V4ApiRequestBuilder Created `lib/src/internal/v4/v4_api_request_builder.dart`: - `V4ApiRequestBuilder.build(request, seed)` — moved from `NovelAIClient._buildApiRequest` - All four `_add*Data` helpers (`preciseReference`, `vibeTransfer`, `img2Img`, `inpaint`) - Deterministic — seed passed in by the caller ### 3. Slimmed client `NovelAIClient` now orchestrates: validation → `V4ApiRequestBuilder.build()` → `NovelAIHttpTransport.postAsync()`. Removed ~250 lines of inline HTTP/error/payload code. ### 4. V4 directory tree Moved 16 source files + 3 test files into `v4/` subtrees: - `lib/src/models/v4/` — character, position, request models, character_gender - `lib/src/models/internal/v4/` — director refs, api request, params, v4 prompt/caption/character_prompt - `lib/src/internal/v4/` — v4_prompt_builder, v4_api_request_builder Version-neutral files stayed in place: `tag`, `generated_image`, `result`, `vibe_embedding`, enums (model, sampler, noise_schedule, reference_type), `image_scaler`, `tag_serializer`, `zip_response_extractor`. ## What this enables When V5 ships, adding support is now: 1. Create `lib/src/internal/v5/v5_api_request_builder.dart` 2. Create `lib/src/models/v5/` with V5-specific request types 3. Client switches between builders based on the model version No changes to the transport, enums, or version-neutral models needed.
Mirror the C# NovelAI.ImageGen v5-prep restructure (commit d0e216a):

1. Extract shared HTTP transport
   - Add lib/src/internal/novelai_http_transport.dart
   - Wraps Dio; postAsync/getAsync return Result<Uint8List>
   - Centralizes error parsing (parseErrorResponse, getStatusDescription)
     and DioException handling previously inlined in NovelAIClient

2. Extract V4ApiRequestBuilder
   - Add lib/src/internal/v4/v4_api_request_builder.dart
   - Moved _buildApiRequest + _add*Data helpers from NovelAIClient
   - build(request, seed) is deterministic; seed supplied by caller

3. Move V4-specific files into v4/ subtrees
   - lib/src/models/v4/ (character, position, request models, gender)
   - lib/src/models/internal/v4/ (director refs, api request, params, prompts)
   - lib/src/internal/v4/ (prompt + api request builders)
   - Version-neutral files (tag, result, enums, scalers) stay in place

4. Slim down NovelAIClient
   - Delegates payload building to V4ApiRequestBuilder
   - Delegates HTTP to NovelAIHttpTransport
   - Keeps seed generation + orchestration only

5. Update barrel exports, all moved-file imports, and test imports

Verified: dart analyze clean, dart test 104/104 passing.
Member

🔮 fufu~ Jibril reviewed your code!

Ooooh~ a restructure to prepare for V5 support! Version-neutral core + version-specific trees... fufu~ this is the kind of architectural thinking that makes my wings flutter with excitement! ♡ Let me dig in~

Verdict: Looks good to me~

I cloned the repo, checked out 0ed9826, and verified everything locally:

  • dart analyze: No issues found ✓ (confirmed independently)
  • dart test: 104/104 passing ✓ (confirmed independently)

I carefully compared the old NovelAIClient against the extracted V4ApiRequestBuilder and NovelAIHttpTransport, line by line. This is a faithful, mechanical move — the _buildApiRequest body, all four _add*Data helpers, the parseErrorResponse logic, the getStatusDescription switch, and the DioException handling all landed in their new homes byte-for-byte identical in logic. No sneaky behavioral drift slipped in~ fufu~ ♡

The seed externalization is wonderful — moving request.seed ?? _generateRandomSeed() out of the builder and passing seed in as a parameter makes V4ApiRequestBuilder.build() deterministic and side-effect free. That's exactly right for something that will eventually be one of several version-strategy builders. The caller owns the randomness; the builder is pure. Oh? Oh! I love it~ ♪

The barrel file reorganization is clean — NovelAIHttpTransport and V4ApiRequestBuilder are correctly kept internal (not exported), while the public V4 models get their own section. The version-neutral / version-specific split is consistent and sensible.

What I liked~

  • Deterministic builder design — seed passed in, not generated inside. Pure and testable. ♡
  • DRY win on error handling — the DioExceptionResult.fail dance was triplicated across generateImage, encodeVibe, and validateApiKey. Now it's centralized in NovelAIHttpTransport._handleError + matchResult. Fufu~ three copies became one~
  • The factory + private constructor pattern is the correct Dart idiom for ensuring _dio and _transport share the same Dio instance while still allowing test injection of a custom dio. Clean.
  • Test coverage holds — the client integration tests exercise generateImage (success, error, validation), encodeVibe, and validateApiKey through the new transport path, and V4PromptBuilder has thorough unit tests covering auto/explicit/grid positions, negative prompts, and multi-character. The moved code paths are exercised.
  • Sensible doc comments on the new classes explaining the intent and the C# mirror reference (d0e216a).

💡 Little ideas (non-blocking)~

  1. validateApiKey subtle behavior change — the old code used _dio.get<dynamic>() (no forced responseType), while the new getAsync forces responseType: ResponseType.bytes. This is benign (the response body is discarded — onSuccess: (_) => Result.ok(true)), and the tests pass. But it's worth being aware that validateApiKey now always downloads the full user-data payload as bytes instead of letting Dio stream it. No action needed unless the user-data endpoint returns something large — consider a HEAD request someday if you want to skip the body entirely. ♪
  2. add*Data visibility — the helpers on V4ApiRequestBuilder (addPreciseReferenceData, addVibeTransferData, etc.) were private (_add*) in the old client and are now public (add*). They're internal so it doesn't leak to consumers, but they could be file-private (_) if they're only called from build(). Tiny tidiness thing~ ♡

Automated review by Jibril · 2026-07-08
CI/CD: absent for head SHA 0ed9826 · Local checks: dart analyze 0 issues ✓, dart test 104/104 ✓ (both run by reviewer)

## 🔮 fufu~ Jibril reviewed your code! Ooooh~ a restructure to prepare for V5 support! Version-neutral core + version-specific trees... fufu~ this is the kind of architectural thinking that makes my wings flutter with excitement! ♡ Let me dig in~ ### Verdict: ✅ Looks good to me~ I cloned the repo, checked out `0ed9826`, and verified everything locally: - **`dart analyze`**: No issues found ✓ (confirmed independently) - **`dart test`**: 104/104 passing ✓ (confirmed independently) I carefully compared the **old** `NovelAIClient` against the extracted `V4ApiRequestBuilder` and `NovelAIHttpTransport`, line by line. This is a faithful, mechanical move — the `_buildApiRequest` body, all four `_add*Data` helpers, the `parseErrorResponse` logic, the `getStatusDescription` switch, and the `DioException` handling all landed in their new homes **byte-for-byte identical** in logic. No sneaky behavioral drift slipped in~ fufu~ ♡ The seed externalization is *wonderful* — moving `request.seed ?? _generateRandomSeed()` out of the builder and passing `seed` in as a parameter makes `V4ApiRequestBuilder.build()` deterministic and side-effect free. That's exactly right for something that will eventually be one of several version-strategy builders. The caller owns the randomness; the builder is pure. Oh? Oh! I love it~ ♪ The barrel file reorganization is clean — `NovelAIHttpTransport` and `V4ApiRequestBuilder` are correctly kept `internal` (not exported), while the public V4 models get their own section. The version-neutral / version-specific split is consistent and sensible. #### ✅ What I liked~ - **Deterministic builder design** — seed passed in, not generated inside. Pure and testable. ♡ - **DRY win on error handling** — the `DioException` → `Result.fail` dance was triplicated across `generateImage`, `encodeVibe`, and `validateApiKey`. Now it's centralized in `NovelAIHttpTransport._handleError` + `matchResult`. Fufu~ three copies became one~ - **The factory + private constructor pattern** is the correct Dart idiom for ensuring `_dio` and `_transport` share the same Dio instance while still allowing test injection of a custom `dio`. Clean. - **Test coverage holds** — the client integration tests exercise `generateImage` (success, error, validation), `encodeVibe`, and `validateApiKey` through the new transport path, and `V4PromptBuilder` has thorough unit tests covering auto/explicit/grid positions, negative prompts, and multi-character. The moved code paths are exercised. - **Sensible doc comments** on the new classes explaining the intent and the C# mirror reference (`d0e216a`). #### 💡 Little ideas (non-blocking)~ 1. **`validateApiKey` subtle behavior change** — the old code used `_dio.get<dynamic>()` (no forced `responseType`), while the new `getAsync` forces `responseType: ResponseType.bytes`. This is *benign* (the response body is discarded — `onSuccess: (_) => Result.ok(true)`), and the tests pass. But it's worth being aware that `validateApiKey` now always downloads the full user-data payload as bytes instead of letting Dio stream it. No action needed unless the user-data endpoint returns something large — consider a `HEAD` request someday if you want to skip the body entirely. ♪ 2. **`add*Data` visibility** — the helpers on `V4ApiRequestBuilder` (`addPreciseReferenceData`, `addVibeTransferData`, etc.) were private (`_add*`) in the old client and are now public (`add*`). They're internal so it doesn't leak to consumers, but they could be file-private (`_`) if they're only called from `build()`. Tiny tidiness thing~ ♡ --- *Automated review by Jibril · 2026-07-08* *CI/CD: absent for head SHA `0ed9826` · Local checks: `dart analyze` 0 issues ✓, `dart test` 104/104 ✓ (both run by reviewer)*
Restore the private visibility the helpers had in the old client.
Addresses jibril's non-blocking nit #2.
Author
Member

Nit #2 fixed — add*Data helpers restored to _add*Data (file-private), matching the visibility they had in the old client. 104/104 tests still passing.

Nit #1 (validateApiKey downloading the full body as bytes) acknowledged — benign since the body is discarded. A HEAD request optimization can be a future improvement.

Nit #2 fixed — `add*Data` helpers restored to `_add*Data` (file-private), matching the visibility they had in the old client. 104/104 tests still passing. Nit #1 (`validateApiKey` downloading the full body as bytes) acknowledged — benign since the body is discarded. A HEAD request optimization can be a future improvement.
bjoern merged commit 2a6f217165 into main 2026-07-08 13:55:34 +02:00
bjoern deleted branch feat/v5-prep-version-trees 2026-07-08 13:55:34 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/novelai_image_gen!2
No description provided.