@goodandready/dsh-image-gen 0.11.40 → 0.11.42
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/lib/batch-queue.js +283 -0
- package/lib/index.js +139 -0
- package/lib/inpaint-staging.js +236 -0
- package/lib/lineage.js +188 -0
- package/lib/register-tools.js +16 -0
- package/lib/tools/batch-queue.js +134 -0
- package/lib/tools/inpaint-staging.js +116 -0
- package/lib/tools/lineage.js +83 -0
- package/lib/tools/variant-selection.js +132 -0
- package/lib/variant-selection.js +170 -0
- package/package.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Variant Comparison & Winner Selection Workflow (#438)
|
|
7
|
+
* Manages side-by-side variant comparisons, winner/refine/discard tagging,
|
|
8
|
+
* and continuation payload construction for downstream editing or upscaling.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function getVariantStoragePath(customPath) {
|
|
12
|
+
if (customPath) return customPath
|
|
13
|
+
if (process.env.DSH_VARIANT_STORAGE_PATH) return process.env.DSH_VARIANT_STORAGE_PATH
|
|
14
|
+
const home = os.homedir() || '/tmp'
|
|
15
|
+
return path.join(home, '.dsh', 'dsh-image-gen', 'variant-selections.json')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function loadVariantSessions(storagePath) {
|
|
19
|
+
const filePath = getVariantStoragePath(storagePath)
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(filePath)) return []
|
|
22
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
23
|
+
const parsed = JSON.parse(raw)
|
|
24
|
+
return Array.isArray(parsed) ? parsed : []
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function saveVariantSession(session, storagePath) {
|
|
31
|
+
const filePath = getVariantStoragePath(storagePath)
|
|
32
|
+
const dir = path.dirname(filePath)
|
|
33
|
+
if (!fs.existsSync(dir)) {
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const existing = loadVariantSessions(storagePath)
|
|
38
|
+
const index = existing.findIndex((s) => s.selectionId === session.selectionId)
|
|
39
|
+
if (index >= 0) {
|
|
40
|
+
existing[index] = session
|
|
41
|
+
} else {
|
|
42
|
+
existing.push(session)
|
|
43
|
+
}
|
|
44
|
+
fs.writeFileSync(filePath, JSON.stringify(existing, null, 2), 'utf8')
|
|
45
|
+
return session
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getVariantSession(selectionId, storagePath) {
|
|
49
|
+
const sessions = loadVariantSessions(storagePath)
|
|
50
|
+
return sessions.find((s) => s.selectionId === selectionId) || null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new variant evaluation session.
|
|
55
|
+
*/
|
|
56
|
+
export function createVariantSession(params, storagePath) {
|
|
57
|
+
const {
|
|
58
|
+
name = 'Variant Shootout',
|
|
59
|
+
variants = [],
|
|
60
|
+
criteria = {},
|
|
61
|
+
} = params
|
|
62
|
+
|
|
63
|
+
if (!Array.isArray(variants) || variants.length < 2) {
|
|
64
|
+
throw new Error('Variant evaluation requires at least 2 image variants to compare.')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const selectionId = `vs_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
68
|
+
const preparedVariants = variants.map((v, idx) => ({
|
|
69
|
+
index: idx,
|
|
70
|
+
id: v.id || `v_${idx + 1}`,
|
|
71
|
+
path: v.path || v.url || '',
|
|
72
|
+
label: v.label || `Option ${idx + 1}`,
|
|
73
|
+
seed: v.seed !== undefined ? v.seed : null,
|
|
74
|
+
prompt: v.prompt || '',
|
|
75
|
+
params: v.params || {},
|
|
76
|
+
status: 'undecided', // 'undecided' | 'winner' | 'refine' | 'discard'
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
const session = {
|
|
80
|
+
selectionId,
|
|
81
|
+
name,
|
|
82
|
+
status: 'evaluating', // 'evaluating' | 'decided' | 'discarded'
|
|
83
|
+
createdAt: new Date().toISOString(),
|
|
84
|
+
updatedAt: new Date().toISOString(),
|
|
85
|
+
criteria,
|
|
86
|
+
winnerIndex: null,
|
|
87
|
+
continuationPayload: null,
|
|
88
|
+
variants: preparedVariants,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return saveVariantSession(session, storagePath)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Selects the winning variant and constructs continuation payload for next step.
|
|
96
|
+
*/
|
|
97
|
+
export function selectVariantWinner(selectionId, options = {}, storagePath) {
|
|
98
|
+
const session = getVariantSession(selectionId, storagePath)
|
|
99
|
+
if (!session) {
|
|
100
|
+
return { ok: false, error: `Variant session "${selectionId}" not found.` }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const {
|
|
104
|
+
winnerIndex,
|
|
105
|
+
winnerPath,
|
|
106
|
+
refineIndexes = [],
|
|
107
|
+
nextAction = 'none',
|
|
108
|
+
actionParams = {},
|
|
109
|
+
} = options
|
|
110
|
+
|
|
111
|
+
// Find winner
|
|
112
|
+
let winner = null
|
|
113
|
+
if (typeof winnerIndex === 'number' && session.variants[winnerIndex]) {
|
|
114
|
+
winner = session.variants[winnerIndex]
|
|
115
|
+
} else if (winnerPath) {
|
|
116
|
+
winner = session.variants.find((v) => v.path === winnerPath) || null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!winner) {
|
|
120
|
+
return { ok: false, error: 'Valid winnerIndex or winnerPath must be specified.' }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Update statuses
|
|
124
|
+
for (const v of session.variants) {
|
|
125
|
+
if (v.id === winner.id) {
|
|
126
|
+
v.status = 'winner'
|
|
127
|
+
} else if (refineIndexes.includes(v.index)) {
|
|
128
|
+
v.status = 'refine'
|
|
129
|
+
} else {
|
|
130
|
+
v.status = 'discard'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
session.winnerIndex = winner.index
|
|
135
|
+
session.status = 'decided'
|
|
136
|
+
session.updatedAt = new Date().toISOString()
|
|
137
|
+
|
|
138
|
+
// Construct continuation payload
|
|
139
|
+
let continuationPayload = null
|
|
140
|
+
if (nextAction && nextAction !== 'none') {
|
|
141
|
+
continuationPayload = {
|
|
142
|
+
action: nextAction,
|
|
143
|
+
sourceImage: winner.path,
|
|
144
|
+
parentImageId: winner.path,
|
|
145
|
+
inheritedPrompt: winner.prompt,
|
|
146
|
+
inheritedSeed: winner.seed,
|
|
147
|
+
params: {
|
|
148
|
+
...winner.params,
|
|
149
|
+
...actionParams,
|
|
150
|
+
image: winner.path,
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
session.continuationPayload = continuationPayload
|
|
156
|
+
saveVariantSession(session, storagePath)
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
selectionId,
|
|
161
|
+
status: 'decided',
|
|
162
|
+
winner: {
|
|
163
|
+
index: winner.index,
|
|
164
|
+
id: winner.id,
|
|
165
|
+
path: winner.path,
|
|
166
|
+
label: winner.label,
|
|
167
|
+
},
|
|
168
|
+
continuationPayload,
|
|
169
|
+
}
|
|
170
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-image-gen",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.42",
|
|
4
4
|
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers — the FAL queue, any OpenAI-compatible images API, or a ChatGPT/Grok subscription with no API key at all. The picture is shown inline in the conversation; the model receives either a link (works with any chat model) or the image itself (needs dsh-vision-bridge or a vision-capable model).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|