@goodandready/dsh-image-gen 0.10.31 → 0.10.32

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.
@@ -0,0 +1,103 @@
1
+ // anchor-helpers.js — Character & Style Reference Anchor manager (#283).
2
+ // Maintains active visual identity and style reference across multi-turn sessions.
3
+
4
+ /**
5
+ * In-memory registry of active visual anchors keyed by scopeId (e.g. session id).
6
+ * Structure: Map<string, { image: string, label: string, mode: 'character'|'style', strength: number, updatedAt: string }>
7
+ */
8
+ const sessionAnchors = new Map()
9
+
10
+ /**
11
+ * Normalizes anchor strength between 0.1 and 1.0.
12
+ *
13
+ * @param {number|undefined} val
14
+ * @returns {number}
15
+ */
16
+ export function normalizeAnchorStrength(val) {
17
+ if (typeof val !== 'number' || Number.isNaN(val)) return 0.65
18
+ return Math.min(1.0, Math.max(0.1, Number(val.toFixed(2))))
19
+ }
20
+
21
+ /**
22
+ * Sets or updates the active visual anchor for a given session or scope.
23
+ *
24
+ * @param {string} scopeId Session or workspace identifier
25
+ * @param {object} data
26
+ * @param {string} data.image Image URL, attachment ID (sha256:...), or local path
27
+ * @param {string} [data.label] Human-readable description of character/style
28
+ * @param {'character'|'style'} [data.mode='style']
29
+ * @param {number} [data.strength=0.65]
30
+ * @returns {object} The stored anchor record
31
+ */
32
+ export function setSessionAnchor(scopeId, data = {}) {
33
+ const key = String(scopeId || 'default')
34
+ if (!data.image) {
35
+ throw new Error('An anchor image (URL, path, or attachment ID) is required.')
36
+ }
37
+
38
+ const mode = data.mode === 'character' ? 'character' : 'style'
39
+ const strength = normalizeAnchorStrength(data.strength)
40
+ const label = String(data.label || (mode === 'character' ? 'Character Anchor' : 'Style Anchor')).trim()
41
+
42
+ const anchor = {
43
+ image: String(data.image).trim(),
44
+ label,
45
+ mode,
46
+ strength,
47
+ updatedAt: new Date().toISOString(),
48
+ }
49
+
50
+ sessionAnchors.set(key, anchor)
51
+ return anchor
52
+ }
53
+
54
+ /**
55
+ * Retrieves the currently active visual anchor for a session scope.
56
+ *
57
+ * @param {string} scopeId
58
+ * @returns {object|null}
59
+ */
60
+ export function getSessionAnchor(scopeId) {
61
+ const key = String(scopeId || 'default')
62
+ return sessionAnchors.get(key) || null
63
+ }
64
+
65
+ /**
66
+ * Clears the active visual anchor for a session scope.
67
+ *
68
+ * @param {string} scopeId
69
+ * @returns {boolean} True if an anchor was present and removed
70
+ */
71
+ export function clearSessionAnchor(scopeId) {
72
+ const key = String(scopeId || 'default')
73
+ return sessionAnchors.delete(key)
74
+ }
75
+
76
+ /**
77
+ * Clears all active anchors (for testing/cleanup).
78
+ */
79
+ export function clearAllAnchors() {
80
+ sessionAnchors.clear()
81
+ }
82
+
83
+ /**
84
+ * Injects anchor reference hints into the prompt if an anchor is active and not already included.
85
+ *
86
+ * @param {object|null} anchor
87
+ * @param {string} prompt
88
+ * @returns {string}
89
+ */
90
+ export function applyAnchorPromptHints(anchor, prompt) {
91
+ if (!anchor || !anchor.label) return prompt
92
+ const lower = prompt.toLowerCase()
93
+ const labelLower = anchor.label.toLowerCase()
94
+
95
+ // Avoid duplicate injection if user already mentioned the anchor label
96
+ if (lower.includes(labelLower)) return prompt
97
+
98
+ if (anchor.mode === 'character') {
99
+ return `${prompt}, visual character anchor: ${anchor.label}, maintaining consistent facial features, costume, and physical identity`
100
+ }
101
+
102
+ return `${prompt}, visual style anchor: ${anchor.label}, maintaining consistent aesthetic palette, rendering technique, and lighting`
103
+ }