From 4e62adaa0ed6c57b9c6ea38c2df029e4eb41a20c Mon Sep 17 00:00:00 2001 From: jcode runner Date: Fri, 31 Jul 2026 22:59:42 +0000 Subject: [PATCH] [jcode] Add H3 camera movement preset chips --- README.md | 31 ++++ .../client/src/features/Composer.test.tsx | 100 +++++++++++- packages/client/src/features/Composer.tsx | 152 +++++++++++++++--- packages/client/src/styles.css | 31 ++++ .../server/src/__tests__/services.test.ts | 68 ++++++++ packages/server/src/__tests__/util.test.ts | 6 + .../server/src/services/generationService.ts | 35 +++- packages/server/src/util.ts | 3 + .../src/__tests__/cameraPresets.test.ts | 117 ++++++++++++++ packages/shared/src/__tests__/schemas.test.ts | 13 ++ packages/shared/src/cameraPresets.ts | 141 ++++++++++++++++ packages/shared/src/index.ts | 1 + packages/shared/src/schemas.ts | 8 + 13 files changed, 682 insertions(+), 24 deletions(-) create mode 100644 packages/shared/src/__tests__/cameraPresets.test.ts create mode 100644 packages/shared/src/cameraPresets.ts diff --git a/README.md b/README.md index 7c224f5..da84632 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ configured. - **Versioned prompt library** — immutable versions, restore-as-new-head, duplicate, archive (never delete), full-text + tag/status filters. +- **H3 camera-motion chips** — one-click camera cues (Pan left/right, Push in, + Pull out, Tracking shot, Static shot) insert at the prompt cursor in the + generation composer, preserving surrounding text; the cue-augmented prompt is + still validated through the H3 policy before submission. - **Pure template engine** — `{{variable}}` parsing/rendering with name validation (letters, numbers, `_`, `.`, `-`), duplicate normalization, and rejection of blank/unresolved variables. @@ -117,6 +121,33 @@ curl -X PUT localhost:3001/api/debug/mock -H 'Content-Type: application/json' \ --- +## Camera-movement preset chips (composer) + +MiniMax's H3 guide recommends camera-motion cues (pan, push/pull, tracking, +static). The generation composer offers them as keyboard-reachable chips so you +don't have to remember or retype the phrasing: + +- Open a prompt version → **Generate from head** to reach the composer. +- The **Prompt** card lists the chips: **Pan left, Pan right, Push in, Pull out, + Tracking shot, Static shot**. Each is a real button (`Tab` to reach, `Enter`/ + `Space` to activate) with a visible focus ring and a tooltip describing the + motion. +- Activating a chip inserts its token at the current cursor (or replaces the + current selection) without disturbing the surrounding text; before you place + the cursor it appends at the end. You can then edit the prompt freely. +- While the prompt is untouched it mirrors the rendered template (filling a + variable live-updates it). The first chip insert or manual edit freezes it as + the source of truth; **Reset to rendered** re-syncs it from the variables. +- The exact text shown is what is generated, sent as a `prompt` override and + still validated through the existing H3 request policy (the 7000-character + limit, duration, ratio, and media rules) before submission. + +The preset labels and inserted tokens live in one pure, tested module +(`packages/shared/src/cameraPresets.ts`) so they are not duplicated across the +UI. + +--- + ## Using the real MiniMax H3 API Real mode is selected by configuration and **fails visibly** when the key is diff --git a/packages/client/src/features/Composer.test.tsx b/packages/client/src/features/Composer.test.tsx index 1e1bb6a..10d455b 100644 --- a/packages/client/src/features/Composer.test.tsx +++ b/packages/client/src/features/Composer.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { NavProvider } from '../nav.js'; import { Composer } from './Composer.js'; @@ -128,3 +128,101 @@ describe('Composer', () => { ); }); }); + +describe('Composer camera-movement preset chips', () => { + it('renders the six presets as keyboard-reachable buttons with names', async () => { + renderComposer(); + await waitFor(() => expect(screen.getByLabelText('subject')).toBeInTheDocument()); + + const group = screen.getByRole('group', { name: /camera movement presets/i }); + const chips = within(group).getAllByRole('button'); + expect(chips).toHaveLength(6); + // Each chip is a real + ) : null} + + +
- {preview} - + {CAMERA_PRESETS.map((preset) => ( + + ))} +
+ + +