@goodandready/dsh-image-gen 0.11.11 → 0.11.13
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/README.md +8 -0
- package/lib/anchor-helpers.js +38 -9
- package/lib/character-sheet-helpers.js +115 -0
- package/lib/client.js +86 -13
- package/lib/diagram-helpers.js +105 -0
- package/lib/index.js +4 -1
- package/lib/providers.js +7 -3
- package/lib/register-tools.js +7 -1
- package/lib/security.js +5 -4
- package/lib/settings-route.js +1 -1
- package/lib/tools/anchor.js +2 -2
- package/lib/tools/character-sheet.js +183 -0
- package/lib/tools/diagram.js +168 -0
- package/lib/tools/generation.js +46 -22
- package/lib/tools/style-matrix.js +1 -1
- package/lib/tools/ui-asset.js +1 -1
- package/lib/tools/vision-ocr.js +277 -0
- package/lib/updater.js +1 -1
- package/lib/vision-ocr-helpers.js +119 -0
- package/package.json +2 -2
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// character-sheet.js — generate_character_sheet tool (#181).
|
|
2
|
+
// Generates multi-angle character model sheets and turnarounds with visual consistency.
|
|
3
|
+
|
|
4
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
7
|
+
import {
|
|
8
|
+
ASPECT_RATIOS,
|
|
9
|
+
buildSidecar,
|
|
10
|
+
makeProviders,
|
|
11
|
+
toLosslessJson,
|
|
12
|
+
saveAttachmentSafe,
|
|
13
|
+
} from '../providers.js'
|
|
14
|
+
import { resolveFallbackChain, executeWithFallback } from '../fallback-router.js'
|
|
15
|
+
import { renderToolOutput } from '../attachment-helper.js'
|
|
16
|
+
import {
|
|
17
|
+
CHARACTER_SHEET_LAYOUTS,
|
|
18
|
+
CHARACTER_SHEET_STYLES,
|
|
19
|
+
buildCharacterSheetPrompt,
|
|
20
|
+
} from '../character-sheet-helpers.js'
|
|
21
|
+
|
|
22
|
+
export function registerCharacterSheetTools(ctx, deps) {
|
|
23
|
+
const {
|
|
24
|
+
live,
|
|
25
|
+
slugify,
|
|
26
|
+
resolveApiKey,
|
|
27
|
+
} = deps
|
|
28
|
+
|
|
29
|
+
ctx.effect(() => {
|
|
30
|
+
ctx.tools.register(
|
|
31
|
+
defineTool({
|
|
32
|
+
name: 'generate_character_sheet',
|
|
33
|
+
description:
|
|
34
|
+
'Generate a multi-angle character model sheet or turnaround reference with high visual identity consistency. '
|
|
35
|
+
+ 'Supports horizontal turnarounds (front, profile, 3/4, back), 1x3 triptychs, 2x2 grids, and facial emotion sheets across concept, anime, 3D, and comic styles.',
|
|
36
|
+
parameters: {
|
|
37
|
+
prompt: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Core character description (e.g. "female cyborg detective with neon violet hair, yellow trenchcoat, cybernetic arm").',
|
|
40
|
+
},
|
|
41
|
+
layout: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
enum: ['turnaround', '1x3', '2x2', 'emotions'],
|
|
44
|
+
description: 'Layout arrangement: "turnaround" (4 sequential horizontal views), "1x3" (front, side, back), "2x2" (quad angles), or "emotions" (4 facial expressions). Default: turnaround.',
|
|
45
|
+
},
|
|
46
|
+
style: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
enum: ['concept_art', 'anime', '3d_animation', 'pixel_art', 'realistic', 'comic'],
|
|
49
|
+
description: 'Artistic rendering style. Default: concept_art.',
|
|
50
|
+
},
|
|
51
|
+
negativePrompt: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Negative prompt directives to suppress unwanted elements.',
|
|
54
|
+
},
|
|
55
|
+
seed: {
|
|
56
|
+
type: 'integer',
|
|
57
|
+
description: 'RNG seed for reproducible character generation across sessions.',
|
|
58
|
+
},
|
|
59
|
+
provider: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Optional provider override (e.g. fal, replicate, local). Defaults to active provider.',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
output: {
|
|
65
|
+
schema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
additionalProperties: true,
|
|
68
|
+
properties: {
|
|
69
|
+
prompt: { type: 'string' },
|
|
70
|
+
layout: { type: 'string' },
|
|
71
|
+
style: { type: 'string' },
|
|
72
|
+
aspectRatio: { type: 'string' },
|
|
73
|
+
views: {
|
|
74
|
+
type: 'array',
|
|
75
|
+
items: { type: 'string' },
|
|
76
|
+
},
|
|
77
|
+
seed: { type: 'integer' },
|
|
78
|
+
provider: { type: 'string' },
|
|
79
|
+
attachment: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
additionalProperties: true,
|
|
82
|
+
properties: {
|
|
83
|
+
attachmentId: { type: 'string' },
|
|
84
|
+
url: { type: 'string' },
|
|
85
|
+
mediaType: { type: 'string' },
|
|
86
|
+
filename: { type: 'string' },
|
|
87
|
+
byteSize: { type: 'integer' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
render: (_args, value) => renderToolOutput(value),
|
|
93
|
+
},
|
|
94
|
+
async execute(args, execCtx) {
|
|
95
|
+
const cfg = live.value
|
|
96
|
+
const activeProvider = args.provider || cfg.activeProvider || 'fal'
|
|
97
|
+
const seed = typeof args.seed === 'number' ? args.seed : Math.floor(Math.random() * 2147483647)
|
|
98
|
+
|
|
99
|
+
const sheetPlan = buildCharacterSheetPrompt({
|
|
100
|
+
prompt: args.prompt,
|
|
101
|
+
layout: args.layout,
|
|
102
|
+
style: args.style,
|
|
103
|
+
negativePrompt: args.negativePrompt,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const providers = makeProviders({
|
|
107
|
+
fetchImpl: fetch,
|
|
108
|
+
resolveKey: (id) => resolveApiKey(cfg, id),
|
|
109
|
+
cfg,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const fallbackChain = resolveFallbackChain(activeProvider, cfg)
|
|
113
|
+
|
|
114
|
+
const aspectPixels = ASPECT_RATIOS[sheetPlan.aspectRatio] || ASPECT_RATIOS['16:9']
|
|
115
|
+
|
|
116
|
+
const jobPayload = {
|
|
117
|
+
prompt: sheetPlan.prompt,
|
|
118
|
+
negativePrompt: sheetPlan.negativePrompt,
|
|
119
|
+
aspectRatio: sheetPlan.aspectRatio,
|
|
120
|
+
aspectPixels,
|
|
121
|
+
format: 'png',
|
|
122
|
+
seed,
|
|
123
|
+
quality: 'high',
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { result: genResult, provider: usedProvider } = await executeWithFallback(
|
|
127
|
+
fallbackChain,
|
|
128
|
+
providers,
|
|
129
|
+
jobPayload,
|
|
130
|
+
{ signal: execCtx?.signal }
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const timestamp = Date.now()
|
|
134
|
+
const safeSlug = slugify(args.prompt.slice(0, 30)) || 'character-sheet'
|
|
135
|
+
const filename = `character-sheet-${sheetPlan.layout}-${safeSlug}-${timestamp}.png`
|
|
136
|
+
|
|
137
|
+
let attachment = null
|
|
138
|
+
if (genResult.bytes) {
|
|
139
|
+
const outDir = path.resolve(process.cwd(), cfg.outputDir || 'output')
|
|
140
|
+
await mkdir(outDir, { recursive: true })
|
|
141
|
+
const diskPath = path.join(outDir, filename)
|
|
142
|
+
await writeFile(diskPath, Buffer.from(genResult.bytes))
|
|
143
|
+
|
|
144
|
+
attachment = await saveAttachmentSafe(
|
|
145
|
+
ctx,
|
|
146
|
+
Buffer.from(genResult.bytes),
|
|
147
|
+
'image/png',
|
|
148
|
+
filename,
|
|
149
|
+
diskPath
|
|
150
|
+
)
|
|
151
|
+
} else if (genResult.url) {
|
|
152
|
+
attachment = {
|
|
153
|
+
url: genResult.url,
|
|
154
|
+
mediaType: 'image/png',
|
|
155
|
+
filename,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const response = {
|
|
160
|
+
prompt: sheetPlan.prompt,
|
|
161
|
+
characterDescription: args.prompt,
|
|
162
|
+
layout: sheetPlan.layout,
|
|
163
|
+
style: sheetPlan.style,
|
|
164
|
+
aspectRatio: sheetPlan.aspectRatio,
|
|
165
|
+
views: sheetPlan.views,
|
|
166
|
+
seed,
|
|
167
|
+
provider: usedProvider,
|
|
168
|
+
attachment,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (attachment?.attachmentId) {
|
|
172
|
+
response.attachmentId = attachment.attachmentId
|
|
173
|
+
}
|
|
174
|
+
if (attachment?.url) {
|
|
175
|
+
response.url = attachment.url
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return response
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
)
|
|
182
|
+
}, 'dsh-image-gen: tool generate_character_sheet')
|
|
183
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// diagram.js — beautify_diagram tool (#185).
|
|
2
|
+
// Transforms raw Mermaid / Graphviz diagrams into high-end 3D isometric architectural illustrations.
|
|
3
|
+
|
|
4
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
7
|
+
import {
|
|
8
|
+
ASPECT_RATIOS,
|
|
9
|
+
makeProviders,
|
|
10
|
+
saveAttachmentSafe,
|
|
11
|
+
} from '../providers.js'
|
|
12
|
+
import { resolveFallbackChain, executeWithFallback } from '../fallback-router.js'
|
|
13
|
+
import { renderToolOutput } from '../attachment-helper.js'
|
|
14
|
+
import {
|
|
15
|
+
DIAGRAM_3D_STYLES,
|
|
16
|
+
buildDiagramIllustrationPrompt,
|
|
17
|
+
} from '../diagram-helpers.js'
|
|
18
|
+
|
|
19
|
+
export function registerDiagramTools(ctx, deps) {
|
|
20
|
+
const {
|
|
21
|
+
live,
|
|
22
|
+
slugify,
|
|
23
|
+
resolveApiKey,
|
|
24
|
+
} = deps
|
|
25
|
+
|
|
26
|
+
ctx.effect(() => {
|
|
27
|
+
ctx.tools.register(
|
|
28
|
+
defineTool({
|
|
29
|
+
name: 'beautify_diagram',
|
|
30
|
+
description:
|
|
31
|
+
'Transform raw Mermaid or Graphviz diagrams into stunning 3D isometric architectural illustrations '
|
|
32
|
+
+ 'with glowing data pipes, glassmorphism cards, and pristine technical lighting for documentation and hero blocks.',
|
|
33
|
+
parameters: {
|
|
34
|
+
diagram: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'Raw Mermaid diagram source code, Graphviz DOT text, or image path/attachment.',
|
|
37
|
+
},
|
|
38
|
+
style: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: ['isometric_3d', 'cyber_blueprint', 'clay_minimal', 'glossy_dark'],
|
|
41
|
+
description: '3D rendering aesthetic style. Default: isometric_3d.',
|
|
42
|
+
},
|
|
43
|
+
title: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Optional architecture title or headline to feature in the composition.',
|
|
46
|
+
},
|
|
47
|
+
aspect_ratio: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
enum: ['16:9', '1:1', '4:3', '21:9'],
|
|
50
|
+
description: 'Aspect ratio for documentation header or hero banner (default: 16:9).',
|
|
51
|
+
},
|
|
52
|
+
seed: {
|
|
53
|
+
type: 'integer',
|
|
54
|
+
description: 'Optional seed for reproducible diagram generation.',
|
|
55
|
+
},
|
|
56
|
+
provider: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Optional provider override. Defaults to active provider.',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
output: {
|
|
62
|
+
schema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
additionalProperties: true,
|
|
65
|
+
properties: {
|
|
66
|
+
title: { type: 'string' },
|
|
67
|
+
style: { type: 'string' },
|
|
68
|
+
aspectRatio: { type: 'string' },
|
|
69
|
+
detectedNodes: {
|
|
70
|
+
type: 'array',
|
|
71
|
+
items: { type: 'string' },
|
|
72
|
+
},
|
|
73
|
+
prompt: { type: 'string' },
|
|
74
|
+
seed: { type: 'integer' },
|
|
75
|
+
provider: { type: 'string' },
|
|
76
|
+
attachment: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
additionalProperties: true,
|
|
79
|
+
properties: {
|
|
80
|
+
attachmentId: { type: 'string' },
|
|
81
|
+
url: { type: 'string' },
|
|
82
|
+
mediaType: { type: 'string' },
|
|
83
|
+
filename: { type: 'string' },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
render: (_args, value) => renderToolOutput(value),
|
|
89
|
+
},
|
|
90
|
+
async execute(args, execCtx) {
|
|
91
|
+
const cfg = live?.value || (typeof live === 'function' ? live() : {})
|
|
92
|
+
const activeProvider = args.provider || cfg.activeProvider || 'fal'
|
|
93
|
+
const seed = typeof args.seed === 'number' ? args.seed : Math.floor(Math.random() * 2147483647)
|
|
94
|
+
|
|
95
|
+
const plan = buildDiagramIllustrationPrompt({
|
|
96
|
+
diagram: args.diagram,
|
|
97
|
+
style: args.style,
|
|
98
|
+
title: args.title,
|
|
99
|
+
aspectRatio: args.aspect_ratio || '16:9',
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const providers = makeProviders({
|
|
103
|
+
fetchImpl: fetch,
|
|
104
|
+
resolveKey: (id) => resolveApiKey(cfg, id),
|
|
105
|
+
cfg,
|
|
106
|
+
})
|
|
107
|
+
const fallbackChain = resolveFallbackChain(activeProvider, cfg)
|
|
108
|
+
const aspectPixels = ASPECT_RATIOS[plan.aspectRatio] || ASPECT_RATIOS['16:9']
|
|
109
|
+
|
|
110
|
+
const jobPayload = {
|
|
111
|
+
prompt: plan.prompt,
|
|
112
|
+
negativePrompt: plan.negativePrompt,
|
|
113
|
+
aspectRatio: plan.aspectRatio,
|
|
114
|
+
aspectPixels,
|
|
115
|
+
format: 'png',
|
|
116
|
+
seed,
|
|
117
|
+
quality: 'high',
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { result: genResult, provider: usedProvider } = await executeWithFallback(
|
|
121
|
+
fallbackChain,
|
|
122
|
+
providers,
|
|
123
|
+
jobPayload,
|
|
124
|
+
{ signal: execCtx?.signal }
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const outDir = path.resolve(process.cwd(), cfg.outputDir || 'output')
|
|
128
|
+
await mkdir(outDir, { recursive: true })
|
|
129
|
+
const timestamp = Date.now()
|
|
130
|
+
const safeSlug = slugify(args.title || 'architecture-diagram') || 'diagram'
|
|
131
|
+
const filename = `diagram-3d-${safeSlug}-${timestamp}.png`
|
|
132
|
+
|
|
133
|
+
let attachment = null
|
|
134
|
+
if (genResult.bytes) {
|
|
135
|
+
const diskPath = path.join(outDir, filename)
|
|
136
|
+
await writeFile(diskPath, Buffer.from(genResult.bytes))
|
|
137
|
+
attachment = await saveAttachmentSafe(
|
|
138
|
+
ctx,
|
|
139
|
+
Buffer.from(genResult.bytes),
|
|
140
|
+
'image/png',
|
|
141
|
+
filename,
|
|
142
|
+
diskPath
|
|
143
|
+
)
|
|
144
|
+
} else if (genResult.url) {
|
|
145
|
+
attachment = {
|
|
146
|
+
url: genResult.url,
|
|
147
|
+
mediaType: 'image/png',
|
|
148
|
+
filename,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
title: args.title || 'System Architecture',
|
|
154
|
+
style: plan.style,
|
|
155
|
+
aspectRatio: plan.aspectRatio,
|
|
156
|
+
detectedNodes: plan.parsedNodes,
|
|
157
|
+
prompt: plan.prompt,
|
|
158
|
+
seed,
|
|
159
|
+
provider: usedProvider,
|
|
160
|
+
attachment,
|
|
161
|
+
url: attachment?.url,
|
|
162
|
+
attachmentId: attachment?.attachmentId,
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
)
|
|
167
|
+
}, 'dsh-image-gen: tool beautify_diagram')
|
|
168
|
+
}
|
package/lib/tools/generation.js
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
import { registerGenerationPackTool } from './generation-pack.js'
|
|
2
|
-
|
|
3
|
-
/** Run an array of async task functions with a concurrency cap */
|
|
4
|
-
async function asyncPool(tasks, concurrency = 3) {
|
|
5
|
-
const results = new Array(tasks.length)
|
|
6
|
-
let nextIdx = 0
|
|
7
|
-
async function worker() {
|
|
8
|
-
while (nextIdx < tasks.length) {
|
|
9
|
-
const idx = nextIdx++
|
|
10
|
-
results[idx] = await tasks[idx]()
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker())
|
|
14
|
-
await Promise.all(workers)
|
|
15
|
-
return results
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// generation — image-gen tools (generate_image, generate_image_pack). Extracted from apply() (#216).
|
|
19
|
-
|
|
20
2
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
21
3
|
import path from 'node:path'
|
|
22
4
|
import { mkdir, writeFile } from 'node:fs/promises'
|
|
@@ -41,7 +23,7 @@ import { calculateGenerationCost, recordSpend, assertBudgetAvailable } from '../
|
|
|
41
23
|
import { trackAndAssertLoopGuard } from '../loop-guard.js'
|
|
42
24
|
import { getCachedGeneration, setCachedGeneration } from '../generation-cache.js'
|
|
43
25
|
import { resolveFallbackChain, executeWithFallback } from '../fallback-router.js'
|
|
44
|
-
import { getSessionAnchor, applyAnchorPromptHints } from '../anchor-helpers.js'
|
|
26
|
+
import { getSessionAnchor, applyAnchorPromptHints, buildFaceConsistencyPrompt } from '../anchor-helpers.js'
|
|
45
27
|
import { enhancePrompt } from '../prompt-enhancer.js'
|
|
46
28
|
import {
|
|
47
29
|
readHistory,
|
|
@@ -53,6 +35,22 @@ import {
|
|
|
53
35
|
cachedResult,
|
|
54
36
|
} from '../history.js'
|
|
55
37
|
|
|
38
|
+
// generation — image-gen tools (generate_image, generate_image_pack). Extracted from apply() (#216).
|
|
39
|
+
|
|
40
|
+
/** Run an array of async task functions with a concurrency cap */
|
|
41
|
+
async function asyncPool(tasks, concurrency = 3) {
|
|
42
|
+
const results = new Array(tasks.length)
|
|
43
|
+
let nextIdx = 0
|
|
44
|
+
async function worker() {
|
|
45
|
+
while (nextIdx < tasks.length) {
|
|
46
|
+
const idx = nextIdx++
|
|
47
|
+
results[idx] = await tasks[idx]()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker())
|
|
51
|
+
await Promise.all(workers)
|
|
52
|
+
return results
|
|
53
|
+
}
|
|
56
54
|
|
|
57
55
|
export function registerGenerationTools(ctx, deps) {
|
|
58
56
|
const {
|
|
@@ -172,6 +170,14 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
172
170
|
type: 'number',
|
|
173
171
|
description: 'Optional strength of reference anchor influence (0.1 to 1.0).',
|
|
174
172
|
},
|
|
173
|
+
face_reference: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'Portrait reference image (URL, local path, or attachment id) for facial identity preservation via FaceID / IP-Adapter (#183).',
|
|
176
|
+
},
|
|
177
|
+
face_strength: {
|
|
178
|
+
type: 'number',
|
|
179
|
+
description: 'Facial identity similarity weight between 0.1 and 1.0 (default: 0.75).',
|
|
180
|
+
},
|
|
175
181
|
},
|
|
176
182
|
output: {
|
|
177
183
|
schema: {
|
|
@@ -336,7 +342,7 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
336
342
|
// Subscriptions service is optional: without it, subscription providers
|
|
337
343
|
// gracefully decline while other providers continue functioning.
|
|
338
344
|
let subscriptionImages
|
|
339
|
-
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch { subscriptionImages = undefined }
|
|
345
|
+
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch (_err) { subscriptionImages = undefined }
|
|
340
346
|
|
|
341
347
|
const source = args.source_image ? await resolveSource(ctx, exec, args.source_image) : undefined
|
|
342
348
|
const mask = args.mask ? await resolveSource(ctx, exec, args.mask) : undefined
|
|
@@ -345,7 +351,25 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
345
351
|
const styleInfo = resolveStylePreset(effectiveStyle, args.negative_prompt, args.guidance_scale)
|
|
346
352
|
let effectivePrompt = styleInfo.promptSuffix ? `${enhanced.prompt}, ${styleInfo.promptSuffix}` : enhanced.prompt
|
|
347
353
|
const activeAnchor = getSessionAnchor(sessionId)
|
|
348
|
-
|
|
354
|
+
let resolvedFaceRef
|
|
355
|
+
const faceRefInput = args.face_reference || (activeAnchor && (activeAnchor.mode === 'face' || activeAnchor.mode === 'character') ? activeAnchor.image : undefined)
|
|
356
|
+
if (faceRefInput) {
|
|
357
|
+
try {
|
|
358
|
+
resolvedFaceRef = await resolveSource(ctx, exec, faceRefInput)
|
|
359
|
+
} catch (_err) {
|
|
360
|
+
/* non-fatal: face_reference resolve fallback */
|
|
361
|
+
resolvedFaceRef = { url: faceRefInput }
|
|
362
|
+
}
|
|
363
|
+
const facePlan = buildFaceConsistencyPrompt({
|
|
364
|
+
prompt: effectivePrompt,
|
|
365
|
+
faceStrength: args.face_strength || activeAnchor?.strength,
|
|
366
|
+
characterName: activeAnchor?.label,
|
|
367
|
+
})
|
|
368
|
+
effectivePrompt = facePlan.prompt
|
|
369
|
+
if (facePlan.negativePrompt) {
|
|
370
|
+
effectiveNegative = effectiveNegative ? `${effectiveNegative}, ${facePlan.negativePrompt}` : facePlan.negativePrompt
|
|
371
|
+
}
|
|
372
|
+
} else if (args.reference_image) {
|
|
349
373
|
effectivePrompt = `${effectivePrompt}, visual reference anchor`
|
|
350
374
|
} else if (activeAnchor) {
|
|
351
375
|
effectivePrompt = applyAnchorPromptHints(activeAnchor, effectivePrompt)
|
|
@@ -373,7 +397,7 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
373
397
|
assertBudgetAvailable(estimatedCost, cfg.dailyBudgetUsd)
|
|
374
398
|
const providers = makeProviders(
|
|
375
399
|
{ fetchImpl: fetch, resolveKey: (ref) => resolveApiKey(ctx, ref), cfg, subscriptionImages },
|
|
376
|
-
{ prompt: effectivePrompt, size, format, seed: args.seed, signal: exec.signal, negativePrompt: effectiveNegative, guidanceScale: effectiveGuidance, source, mask, strength: args.strength, quality: args.quality, style: args.style, aspectPixels, aspectRatio: args.aspect_ratio },
|
|
400
|
+
{ prompt: effectivePrompt, size, format, seed: args.seed, signal: exec.signal, negativePrompt: effectiveNegative, guidanceScale: effectiveGuidance, source, mask, strength: args.strength, quality: args.quality, style: args.style, aspectPixels, aspectRatio: args.aspect_ratio, faceReference: resolvedFaceRef },
|
|
377
401
|
)
|
|
378
402
|
// Subscription providers return {ok:false, reason} instead of throwing:
|
|
379
403
|
// rejection reaches the model as text. Without this guard,
|
|
@@ -126,7 +126,7 @@ export function registerStyleMatrixTools(ctx, deps) {
|
|
|
126
126
|
|
|
127
127
|
const provider = PROVIDER_KEYS.includes(cfg.provider) ? cfg.provider : 'fal'
|
|
128
128
|
let subscriptionImages
|
|
129
|
-
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch { subscriptionImages = undefined }
|
|
129
|
+
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch (_err) { subscriptionImages = undefined }
|
|
130
130
|
|
|
131
131
|
const chain = resolveFallbackChain(provider, cfg.fallbackProviders, PROVIDER_KEYS)
|
|
132
132
|
const quadrantIds = ['A', 'B', 'C', 'D']
|
package/lib/tools/ui-asset.js
CHANGED
|
@@ -145,7 +145,7 @@ export function registerUiAssetTools(ctx, deps) {
|
|
|
145
145
|
|
|
146
146
|
const provider = PROVIDER_KEYS.includes(cfg.provider) ? cfg.provider : 'fal'
|
|
147
147
|
let subscriptionImages
|
|
148
|
-
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch { subscriptionImages = undefined }
|
|
148
|
+
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch (_err) { subscriptionImages = undefined }
|
|
149
149
|
|
|
150
150
|
const providers = makeProviders(
|
|
151
151
|
{ fetchImpl: fetch, resolveKey: (ref) => resolveApiKey(ctx, ref), cfg, subscriptionImages },
|