@melite/formats 0.0.0-stage → 0.1.4-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +557 -0
- package/lib/b64.d.ts +2 -0
- package/lib/b64.js +7 -0
- package/lib/bodyPresets.d.ts +40 -0
- package/lib/bodyPresets.js +77 -0
- package/lib/capabilityCards.d.ts +54 -0
- package/lib/capabilityCards.js +127 -0
- package/lib/captionSplitter.d.ts +49 -0
- package/lib/captionSplitter.js +172 -0
- package/lib/cardMutations.d.ts +52 -0
- package/lib/cardMutations.js +67 -0
- package/lib/downloads.d.ts +18 -0
- package/lib/downloads.js +64 -0
- package/lib/extractVideoFrame.d.ts +23 -0
- package/lib/extractVideoFrame.js +114 -0
- package/lib/glbLoad.d.ts +54 -0
- package/lib/glbLoad.js +96 -0
- package/lib/glbSkin.d.ts +14 -0
- package/lib/glbSkin.js +124 -0
- package/lib/handPose.d.ts +46 -0
- package/lib/handPose.js +122 -0
- package/lib/index.d.ts +26 -0
- package/lib/index.js +26 -0
- package/lib/keyframeExtract.d.ts +46 -0
- package/lib/keyframeExtract.js +89 -0
- package/lib/maskPrep.d.ts +88 -0
- package/lib/maskPrep.js +386 -0
- package/lib/motionConstraints.d.ts +237 -0
- package/lib/motionConstraints.js +369 -0
- package/lib/motionEnvelope.d.ts +56 -0
- package/lib/motionEnvelope.js +59 -0
- package/lib/motionNpz.d.ts +120 -0
- package/lib/motionNpz.js +497 -0
- package/lib/npz.d.ts +24 -0
- package/lib/npz.js +158 -0
- package/lib/openPoseCanvas.d.ts +17 -0
- package/lib/openPoseCanvas.js +90 -0
- package/lib/pngMeta.d.ts +42 -0
- package/lib/pngMeta.js +212 -0
- package/lib/pngMetaInfo.d.ts +14 -0
- package/lib/pngMetaInfo.js +127 -0
- package/lib/poseCodec.d.ts +20 -0
- package/lib/poseCodec.js +61 -0
- package/lib/poseTransforms.d.ts +73 -0
- package/lib/poseTransforms.js +237 -0
- package/lib/poserSkin.d.ts +75 -0
- package/lib/poserSkin.js +126 -0
- package/lib/prompt-tags.json +132 -0
- package/lib/promptTags.d.ts +42 -0
- package/lib/promptTags.js +116 -0
- package/lib/psdExport.d.ts +28 -0
- package/lib/psdExport.js +272 -0
- package/lib/readFile.d.ts +9 -0
- package/lib/readFile.js +16 -0
- package/lib/renderSnapshot.d.ts +34 -0
- package/lib/renderSnapshot.js +55 -0
- package/lib/somaSkin.d.ts +82 -0
- package/lib/somaSkin.js +267 -0
- package/lib/textToPose.d.ts +388 -0
- package/lib/textToPose.js +604 -0
- package/lib/uploadToEstate.d.ts +25 -0
- package/lib/uploadToEstate.js +97 -0
- package/package.json +148 -4
- package/README.md +0 -3
package/lib/poserSkin.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import { apiFetch } from "@melite/catalog";
|
|
20
|
+
// Per-coefficient-signature cache — ONE for the whole estate (both
|
|
21
|
+
// studios share it: previewing a shape in Body Studio primes the exact
|
|
22
|
+
// entry Pose Studio needs, zero network). All shaped meshes share SOMA's
|
|
23
|
+
// 18056-vertex layout; the cache key incorporates identity_model +
|
|
24
|
+
// coeffs + local_changes + custom_targets + preview_pose so identical
|
|
25
|
+
// shapes hit the cache on re-fetch.
|
|
26
|
+
const _meshCache = new Map();
|
|
27
|
+
export async function fetchBindMesh(query = {}) {
|
|
28
|
+
const { identityModel, identityCoeffs, localChanges, previewPose, customTargets, } = query;
|
|
29
|
+
const key = identityModel || "anny";
|
|
30
|
+
// Round coeffs to 1 decimal for the signature - visually-identical shapes
|
|
31
|
+
// share a cache entry without exploding it on every micro-drag.
|
|
32
|
+
const coeffsSig = identityCoeffs && identityCoeffs.length > 0
|
|
33
|
+
? identityCoeffs.map(v => v.toFixed(1)).join(",")
|
|
34
|
+
: null;
|
|
35
|
+
const lcEntries = localChanges && Object.keys(localChanges).length > 0
|
|
36
|
+
? Object.entries(localChanges)
|
|
37
|
+
// Object keys are unique, so the equal-key arm is unreachable:
|
|
38
|
+
// a two-arm comparator sorts identically here (proof: distinct
|
|
39
|
+
// strings are always ordered one way or the other).
|
|
40
|
+
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
41
|
+
.map(([k, v]) => `${k}=${v.toFixed(2)}`).join(",")
|
|
42
|
+
: null;
|
|
43
|
+
// Custom .target blendshape weights (e.g. {"anime_stylized_enhanced": 0.5}).
|
|
44
|
+
// Rounded to 2 decimals - same convention as localChanges.
|
|
45
|
+
const ctEntries = customTargets && Object.keys(customTargets).length > 0
|
|
46
|
+
? Object.entries(customTargets)
|
|
47
|
+
// Same unique-keys proof as lcEntries above: two arms suffice.
|
|
48
|
+
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
49
|
+
.map(([k, v]) => `${k}=${v.toFixed(2)}`).join(",")
|
|
50
|
+
: null;
|
|
51
|
+
const poseSig = previewPose && previewPose !== "tpose" ? previewPose : null;
|
|
52
|
+
const cacheKey = [key, coeffsSig, lcEntries, ctEntries, poseSig].filter(Boolean).join("|");
|
|
53
|
+
const cached = _meshCache.get(cacheKey);
|
|
54
|
+
if (cached)
|
|
55
|
+
return cached;
|
|
56
|
+
const posePayload = poseSig ? { preview_pose: poseSig } : {};
|
|
57
|
+
let data;
|
|
58
|
+
if (coeffsSig || lcEntries || ctEntries) {
|
|
59
|
+
const res = await apiFetch("/v1/poser/mesh", {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
identity_model: key,
|
|
64
|
+
...posePayload,
|
|
65
|
+
...(coeffsSig ? { identity_coeffs: identityCoeffs } : {}),
|
|
66
|
+
...(lcEntries ? { local_changes: localChanges } : {}),
|
|
67
|
+
// Always send custom_targets for Anny POSTs, even when empty.
|
|
68
|
+
// The backend's set_identity treats a MISSING custom_targets
|
|
69
|
+
// field as "preserve previous state" (so phenotype-only edits
|
|
70
|
+
// don't wipe stylization). But the frontend always sends the
|
|
71
|
+
// full desired state - so omitting the field when the user
|
|
72
|
+
// drags a slider to 0 leaves the old weight active (the
|
|
73
|
+
// "slider at 0 shows ~50%" bug). Sending {} explicitly clears.
|
|
74
|
+
...(key === "anny" ? { custom_targets: customTargets ?? {} } : {}),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok)
|
|
78
|
+
throw new Error(`Failed to fetch shaped mesh (${key}): ${res.status}`);
|
|
79
|
+
data = (await res.json());
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const qs = new URLSearchParams({ identity_model: key });
|
|
83
|
+
if (poseSig)
|
|
84
|
+
qs.set("preview_pose", poseSig);
|
|
85
|
+
const res = await apiFetch(`/v1/poser/mesh?${qs.toString()}`);
|
|
86
|
+
if (!res.ok)
|
|
87
|
+
throw new Error(`Failed to fetch mesh (${key}): ${res.status}`);
|
|
88
|
+
data = (await res.json());
|
|
89
|
+
}
|
|
90
|
+
_meshCache.set(cacheKey, data);
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
export async function fetchIdentitySpace(model) {
|
|
94
|
+
const res = await apiFetch(`/v1/poser/identity-space?identity_model=${encodeURIComponent(model)}`);
|
|
95
|
+
if (!res.ok)
|
|
96
|
+
throw new Error(`Failed to fetch identity-space (${model}): ${res.status}`);
|
|
97
|
+
return (await res.json());
|
|
98
|
+
}
|
|
99
|
+
// Lift the bind pose so feet clear the floor (matches Kimodo scene.py:
|
|
100
|
+
// bind_pos.y -= min_height; bind_pos.y += 0.02).
|
|
101
|
+
const FLOOR_LIFT_Y = 0.02;
|
|
102
|
+
// SOMA-X/ANNY mesh vertices arrive in centimeters (~170 units tall). The
|
|
103
|
+
// Three.js scene (camera, grid, lights) is authored in meters (~1.7 units).
|
|
104
|
+
// Convert at ingestion so downstream code never sees raw cm.
|
|
105
|
+
const CM_TO_M = 0.01;
|
|
106
|
+
/** SkinMeshData → flat Float32Array positions (V*3) in METERS with the
|
|
107
|
+
* floor lift applied (the body-studio preview geometry path). */
|
|
108
|
+
export function buildFlatPositions(meshData) {
|
|
109
|
+
const verts = meshData.vertices;
|
|
110
|
+
const arr = new Float32Array(verts.length * 3);
|
|
111
|
+
let minY = Infinity;
|
|
112
|
+
for (let i = 0; i < verts.length; i++) {
|
|
113
|
+
const yMeters = verts[i][1] * CM_TO_M;
|
|
114
|
+
if (yMeters < minY)
|
|
115
|
+
minY = yMeters;
|
|
116
|
+
}
|
|
117
|
+
if (!isFinite(minY))
|
|
118
|
+
minY = 0;
|
|
119
|
+
for (let i = 0; i < verts.length; i++) {
|
|
120
|
+
arr[i * 3] = verts[i][0] * CM_TO_M;
|
|
121
|
+
arr[i * 3 + 1] = verts[i][1] * CM_TO_M - minY + FLOOR_LIFT_Y;
|
|
122
|
+
arr[i * 3 + 2] = verts[i][2] * CM_TO_M;
|
|
123
|
+
}
|
|
124
|
+
return arr;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=poserSkin.js.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "Curated prompt tag library for the editor autocomplete (E4.7). Tags are organized by category so the dropdown can group them. Each tag is a short phrase that reads naturally when appended to a prompt. The categories mirror the typical shot-authoring taxonomy: shot framing, camera movement, lighting, style, subject, mood, color grade.",
|
|
5
|
+
"noteOnIaC": "This file is checked into source control so the tag library survives environment wipes — adding tags here is the only step needed to grow the library."
|
|
6
|
+
},
|
|
7
|
+
"shot": [
|
|
8
|
+
"extreme close-up",
|
|
9
|
+
"close-up",
|
|
10
|
+
"medium close-up",
|
|
11
|
+
"medium shot",
|
|
12
|
+
"medium wide shot",
|
|
13
|
+
"wide shot",
|
|
14
|
+
"extreme wide shot",
|
|
15
|
+
"over-the-shoulder",
|
|
16
|
+
"low angle",
|
|
17
|
+
"high angle",
|
|
18
|
+
"dutch angle",
|
|
19
|
+
"aerial view",
|
|
20
|
+
"birds eye view",
|
|
21
|
+
"pov shot",
|
|
22
|
+
"two shot"
|
|
23
|
+
],
|
|
24
|
+
"camera": [
|
|
25
|
+
"static shot",
|
|
26
|
+
"locked-off",
|
|
27
|
+
"dolly in",
|
|
28
|
+
"dolly out",
|
|
29
|
+
"tracking shot",
|
|
30
|
+
"pan left",
|
|
31
|
+
"pan right",
|
|
32
|
+
"tilt up",
|
|
33
|
+
"tilt down",
|
|
34
|
+
"handheld",
|
|
35
|
+
"steady cam",
|
|
36
|
+
"crane shot",
|
|
37
|
+
"drone shot",
|
|
38
|
+
"whip pan",
|
|
39
|
+
"zoom in",
|
|
40
|
+
"zoom out",
|
|
41
|
+
"orbital shot",
|
|
42
|
+
"parallax"
|
|
43
|
+
],
|
|
44
|
+
"lighting": [
|
|
45
|
+
"golden hour",
|
|
46
|
+
"blue hour",
|
|
47
|
+
"magic hour",
|
|
48
|
+
"soft light",
|
|
49
|
+
"hard light",
|
|
50
|
+
"backlit",
|
|
51
|
+
"rim light",
|
|
52
|
+
"volumetric light",
|
|
53
|
+
"neon glow",
|
|
54
|
+
"candlelight",
|
|
55
|
+
"moonlight",
|
|
56
|
+
"overcast",
|
|
57
|
+
"high key",
|
|
58
|
+
"low key",
|
|
59
|
+
"chiaroscuro",
|
|
60
|
+
"studio lighting",
|
|
61
|
+
"natural light"
|
|
62
|
+
],
|
|
63
|
+
"style": [
|
|
64
|
+
"cinematic",
|
|
65
|
+
"photorealistic",
|
|
66
|
+
"hyperreal",
|
|
67
|
+
"documentary",
|
|
68
|
+
"film noir",
|
|
69
|
+
"anime",
|
|
70
|
+
"watercolor",
|
|
71
|
+
"oil painting",
|
|
72
|
+
"3d render",
|
|
73
|
+
"vintage film",
|
|
74
|
+
"8mm",
|
|
75
|
+
"vhs",
|
|
76
|
+
"cyberpunk",
|
|
77
|
+
"vaporwave",
|
|
78
|
+
"minimalist",
|
|
79
|
+
"maximalist",
|
|
80
|
+
"surreal",
|
|
81
|
+
"dreamlike"
|
|
82
|
+
],
|
|
83
|
+
"motion": [
|
|
84
|
+
"slow motion",
|
|
85
|
+
"fast motion",
|
|
86
|
+
"time-lapse",
|
|
87
|
+
"hyperlapse",
|
|
88
|
+
"freeze frame",
|
|
89
|
+
"match cut",
|
|
90
|
+
"subtle motion",
|
|
91
|
+
"dynamic motion",
|
|
92
|
+
"fluid motion",
|
|
93
|
+
"glitch transition"
|
|
94
|
+
],
|
|
95
|
+
"mood": [
|
|
96
|
+
"serene",
|
|
97
|
+
"tense",
|
|
98
|
+
"melancholic",
|
|
99
|
+
"euphoric",
|
|
100
|
+
"ominous",
|
|
101
|
+
"playful",
|
|
102
|
+
"mysterious",
|
|
103
|
+
"romantic",
|
|
104
|
+
"epic",
|
|
105
|
+
"intimate",
|
|
106
|
+
"lonely",
|
|
107
|
+
"triumphant"
|
|
108
|
+
],
|
|
109
|
+
"color": [
|
|
110
|
+
"teal and orange",
|
|
111
|
+
"desaturated",
|
|
112
|
+
"high contrast",
|
|
113
|
+
"warm tones",
|
|
114
|
+
"cool tones",
|
|
115
|
+
"monochrome",
|
|
116
|
+
"sepia",
|
|
117
|
+
"pastel palette",
|
|
118
|
+
"vivid colors",
|
|
119
|
+
"muted palette",
|
|
120
|
+
"neon palette"
|
|
121
|
+
],
|
|
122
|
+
"subject": [
|
|
123
|
+
"lone figure",
|
|
124
|
+
"crowd",
|
|
125
|
+
"vehicle",
|
|
126
|
+
"landscape",
|
|
127
|
+
"interior",
|
|
128
|
+
"cityscape",
|
|
129
|
+
"nature",
|
|
130
|
+
"abstract"
|
|
131
|
+
]
|
|
132
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type PromptTagCategory = 'shot' | 'camera' | 'lighting' | 'style' | 'motion' | 'mood' | 'color' | 'subject';
|
|
2
|
+
export interface PromptTag {
|
|
3
|
+
/** The phrase as it would be inserted into a prompt. */
|
|
4
|
+
value: string;
|
|
5
|
+
/** Category bucket (for grouping in the dropdown). */
|
|
6
|
+
category: PromptTagCategory;
|
|
7
|
+
}
|
|
8
|
+
/** Category display order for the dropdown (matches the JSON key order). */
|
|
9
|
+
export declare const TAG_CATEGORY_ORDER: readonly PromptTagCategory[];
|
|
10
|
+
/** Human-readable labels for each category. */
|
|
11
|
+
export declare const TAG_CATEGORY_LABELS: Record<PromptTagCategory, string>;
|
|
12
|
+
export declare const ALL_PROMPT_TAGS: readonly PromptTag[];
|
|
13
|
+
/** Case-insensitive containment filter — the workhorse for suggestions. */
|
|
14
|
+
export declare function filterTags(tags: readonly PromptTag[], query: string, limit?: number): PromptTag[];
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export declare function extractCurrentToken(text: string, cursorPos?: number): string;
|
|
29
|
+
/**
|
|
30
|
+
* Insert a tag at the current token position, returning the new text +
|
|
31
|
+
* new cursor position. The token being typed is replaced by the tag
|
|
32
|
+
* value; a comma + space separator is appended so the user can keep
|
|
33
|
+
* adding tags.
|
|
34
|
+
*
|
|
35
|
+
* insertTagAtToken("a cat, gol", 11, "golden hour")
|
|
36
|
+
* → { text: "a cat, golden hour, ", cursor: 21 }
|
|
37
|
+
*/
|
|
38
|
+
export declare function insertTagAtToken(text: string, cursorPos: number, tagValue: string): {
|
|
39
|
+
text: string;
|
|
40
|
+
cursor: number;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=promptTags.d.ts.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E4.7 — Prompt tag library + autocomplete helpers.
|
|
3
|
+
*
|
|
4
|
+
* The tag library lives in `src/data/prompt-tags.json` (checked into
|
|
5
|
+
* source — the IaC piece that survives wipes). This module loads it,
|
|
6
|
+
* flattens it into a searchable list, and exposes filtering helpers
|
|
7
|
+
* the PromptAutocomplete component uses to render suggestions.
|
|
8
|
+
*
|
|
9
|
+
* Tags are short phrases (e.g. "golden hour", "dolly in", "teal and
|
|
10
|
+
* orange") that read naturally when appended to a prompt. The current-
|
|
11
|
+
* token extractor finds the substring being typed after the last comma
|
|
12
|
+
* or semicolon, so suggestions apply to the phrase the user is mid-
|
|
13
|
+
* type on, not the whole prompt.
|
|
14
|
+
*/
|
|
15
|
+
import tagsJson from './prompt-tags.json' with { type: 'json' };
|
|
16
|
+
/** Category display order for the dropdown (matches the JSON key order). */
|
|
17
|
+
export const TAG_CATEGORY_ORDER = [
|
|
18
|
+
'shot', 'camera', 'lighting', 'style',
|
|
19
|
+
'motion', 'mood', 'color', 'subject',
|
|
20
|
+
];
|
|
21
|
+
/** Human-readable labels for each category. */
|
|
22
|
+
export const TAG_CATEGORY_LABELS = {
|
|
23
|
+
shot: 'Shot',
|
|
24
|
+
camera: 'Camera',
|
|
25
|
+
lighting: 'Lighting',
|
|
26
|
+
style: 'Style',
|
|
27
|
+
motion: 'Motion',
|
|
28
|
+
mood: 'Mood',
|
|
29
|
+
color: 'Color',
|
|
30
|
+
subject: 'Subject',
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Flatten the JSON tag map into a single list of { value, category }.
|
|
34
|
+
* Computed once at module load (the JSON is static) and frozen so
|
|
35
|
+
* callers can't mutate the canonical list.
|
|
36
|
+
*/
|
|
37
|
+
function flatten(json) {
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const cat of TAG_CATEGORY_ORDER) {
|
|
40
|
+
const arr = json[cat];
|
|
41
|
+
// Runtime armor for hand-edits that bypass tsc; pinned by
|
|
42
|
+
// promptTagsArmor.spec.ts (mocked malformed database).
|
|
43
|
+
if (!Array.isArray(arr))
|
|
44
|
+
continue;
|
|
45
|
+
for (const value of arr) {
|
|
46
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
47
|
+
out.push({ value, category: cat });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
export const ALL_PROMPT_TAGS = Object.freeze(flatten(tagsJson));
|
|
54
|
+
/** Case-insensitive containment filter — the workhorse for suggestions. */
|
|
55
|
+
export function filterTags(tags, query, limit = 8) {
|
|
56
|
+
const q = query.trim().toLowerCase();
|
|
57
|
+
if (!q)
|
|
58
|
+
return [];
|
|
59
|
+
// Prefer prefix matches first (they read better), then substring.
|
|
60
|
+
const prefix = [];
|
|
61
|
+
const substring = [];
|
|
62
|
+
for (const tag of tags) {
|
|
63
|
+
const v = tag.value.toLowerCase();
|
|
64
|
+
if (v.startsWith(q))
|
|
65
|
+
prefix.push(tag);
|
|
66
|
+
else if (v.includes(q))
|
|
67
|
+
substring.push(tag);
|
|
68
|
+
}
|
|
69
|
+
// Stable order within each bucket (JSON order). Prefix bucket wins.
|
|
70
|
+
return [...prefix, ...substring].slice(0, limit);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
export function extractCurrentToken(text, cursorPos = text.length) {
|
|
86
|
+
const upTo = text.slice(0, cursorPos);
|
|
87
|
+
// Find the last comma or semicolon before the cursor.
|
|
88
|
+
const lastComma = Math.max(upTo.lastIndexOf(','), upTo.lastIndexOf(';'));
|
|
89
|
+
const tokenStart = lastComma === -1 ? 0 : lastComma + 1;
|
|
90
|
+
// ltrim whitespace + newlines.
|
|
91
|
+
return upTo.slice(tokenStart).replace(/^\s+/, '');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Insert a tag at the current token position, returning the new text +
|
|
95
|
+
* new cursor position. The token being typed is replaced by the tag
|
|
96
|
+
* value; a comma + space separator is appended so the user can keep
|
|
97
|
+
* adding tags.
|
|
98
|
+
*
|
|
99
|
+
* insertTagAtToken("a cat, gol", 11, "golden hour")
|
|
100
|
+
* → { text: "a cat, golden hour, ", cursor: 21 }
|
|
101
|
+
*/
|
|
102
|
+
export function insertTagAtToken(text, cursorPos, tagValue) {
|
|
103
|
+
const upTo = text.slice(0, cursorPos);
|
|
104
|
+
const after = text.slice(cursorPos);
|
|
105
|
+
const lastComma = Math.max(upTo.lastIndexOf(','), upTo.lastIndexOf(';'));
|
|
106
|
+
const tokenStart = lastComma === -1 ? 0 : lastComma + 1;
|
|
107
|
+
// Keep the prefix (everything before the current token), trimmed.
|
|
108
|
+
const prefix = upTo.slice(0, tokenStart).trimEnd();
|
|
109
|
+
// If there's a prefix, ensure it ends with a separator + space.
|
|
110
|
+
const sep = prefix.length > 0 ? (prefix.endsWith(',') ? ' ' : ', ') : '';
|
|
111
|
+
const insertion = `${prefix}${sep}${tagValue}, `;
|
|
112
|
+
const newText = insertion + after;
|
|
113
|
+
const newCursor = insertion.length;
|
|
114
|
+
return { text: newText, cursor: newCursor };
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=promptTags.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal layered PSD (Photoshop) writer — 8-bit RGB with per-layer alpha.
|
|
3
|
+
*
|
|
4
|
+
* Produces a single-group PSD where each provided layer becomes a PSD layer
|
|
5
|
+
* (channels R, G, B, transparency mask), plus a flattened composite as the
|
|
6
|
+
* merged "Image Data" section. Channel data is stored raw (uncompressed) for
|
|
7
|
+
* simplicity and maximum reader compatibility. This is a best-effort base
|
|
8
|
+
* implementation — it round-trips in Photoshop, Krita, and GIMP for typical
|
|
9
|
+
* RGB layers but does not support 16-bit, adjustment layers, or smart objects.
|
|
10
|
+
*/
|
|
11
|
+
export type BlendMode = 'normal' | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten' | 'color-dodge' | 'color-burn' | 'hard-light' | 'soft-light' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color' | 'luminosity';
|
|
12
|
+
export interface PsdLayerInput {
|
|
13
|
+
name: string;
|
|
14
|
+
/** RGBA PNG/JPEG dataUrl of the layer content. Null/empty = skip. */
|
|
15
|
+
dataUrl: string | null;
|
|
16
|
+
/** Opacity 0–1. */
|
|
17
|
+
opacity: number;
|
|
18
|
+
blendMode: BlendMode;
|
|
19
|
+
visible: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build a layered PSD Blob. `width`/`height` define the canvas; every layer is
|
|
23
|
+
* composited into that extent. The merged composite flattens visible layers.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildPsd(layers: PsdLayerInput[], width: number, height: number, mergedRgba?: Uint8ClampedArray): Promise<Blob>;
|
|
26
|
+
/** Trigger a browser download for a Blob. */
|
|
27
|
+
export declare function downloadBlob(blob: Blob, filename: string): void;
|
|
28
|
+
//# sourceMappingURL=psdExport.d.ts.map
|