Skip to content

Commit 44e10b4

Browse files
committed
refactor(node): generate image describe intent
1 parent 6db8733 commit 44e10b4

9 files changed

Lines changed: 509 additions & 387 deletions

File tree

packages/node/scripts/generate-intent-commands.ts

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,24 @@ function formatFieldDefinitionsName(spec: ResolvedIntentCommandSpec): string {
2828
return `${spec.className[0]?.toLowerCase() ?? ''}${spec.className.slice(1)}Fields`
2929
}
3030

31-
function formatSchemaFields(
32-
fieldSpecs: GeneratedSchemaField[],
33-
spec: ResolvedIntentCommandSpec,
34-
): string {
35-
return fieldSpecs
31+
function getOptionFields(spec: ResolvedIntentCommandSpec): readonly GeneratedSchemaField[] {
32+
if (spec.execution.kind === 'dynamic-step') {
33+
return spec.execution.fields
34+
}
35+
36+
return spec.fieldSpecs
37+
}
38+
39+
function formatSchemaFields(spec: ResolvedIntentCommandSpec): string {
40+
return getOptionFields(spec)
3641
.map((fieldSpec) => {
3742
return ` ${fieldSpec.propertyName} = createIntentOption(${formatFieldDefinitionsName(spec)}.${fieldSpec.propertyName})`
3843
})
3944
.join('\n\n')
4045
}
4146

42-
function formatFieldDefinitions(
43-
fieldSpecs: GeneratedSchemaField[],
44-
spec: ResolvedIntentCommandSpec,
45-
): string {
47+
function formatFieldDefinitions(spec: ResolvedIntentCommandSpec): string {
48+
const fieldSpecs = getOptionFields(spec)
4649
if (fieldSpecs.length === 0) {
4750
return ''
4851
}
@@ -82,14 +85,18 @@ function getCommandDefinitionName(spec: ResolvedIntentCommandSpec): string {
8285
}
8386

8487
function getBaseClassName(spec: ResolvedIntentCommandSpec): string {
85-
if (spec.input.kind === 'none') {
88+
if (spec.runnerKind === 'no-input') {
8689
return 'GeneratedNoInputIntentCommand'
8790
}
8891

89-
if (spec.input.defaultSingleAssembly) {
92+
if (spec.runnerKind === 'bundled') {
9093
return 'GeneratedBundledFileIntentCommand'
9194
}
9295

96+
if (spec.runnerKind === 'watchable') {
97+
return 'GeneratedWatchableFileIntentCommand'
98+
}
99+
93100
return 'GeneratedStandardFileIntentCommand'
94101
}
95102

@@ -120,6 +127,29 @@ function formatIntentDefinition(spec: ResolvedIntentCommandSpec): string {
120127
} as const`
121128
}
122129

130+
if (spec.execution.kind === 'dynamic-step') {
131+
const commandLabelLine =
132+
spec.input.kind === 'local-files'
133+
? `\n commandLabel: ${JSON.stringify(spec.commandLabel)},`
134+
: ''
135+
const inputPolicyLine =
136+
spec.input.kind === 'local-files'
137+
? `\n inputPolicy: ${JSON.stringify(spec.input.inputPolicy, null, 4).replaceAll('\n', '\n ')},`
138+
: ''
139+
const outputMode =
140+
spec.outputMode == null ? '' : `\n outputMode: ${JSON.stringify(spec.outputMode)},`
141+
142+
return `const ${getCommandDefinitionName(spec)} = {${commandLabelLine}${inputPolicyLine}${outputMode}
143+
outputDescription: ${JSON.stringify(spec.outputDescription)},
144+
execution: {
145+
kind: 'dynamic-step',
146+
handler: ${JSON.stringify(spec.execution.handler)},
147+
fields: Object.values(${formatFieldDefinitionsName(spec)}),
148+
resultStepName: ${JSON.stringify(spec.execution.resultStepName)},
149+
},
150+
} as const`
151+
}
152+
123153
const outputMode =
124154
spec.outputMode == null ? '' : `\n outputMode: ${JSON.stringify(spec.outputMode)},`
125155
return `const ${getCommandDefinitionName(spec)} = {
@@ -134,7 +164,7 @@ function formatIntentDefinition(spec: ResolvedIntentCommandSpec): string {
134164
}
135165

136166
function generateClass(spec: ResolvedIntentCommandSpec): string {
137-
const schemaFields = formatSchemaFields(spec.fieldSpecs, spec)
167+
const schemaFields = formatSchemaFields(spec)
138168
const baseClassName = getBaseClassName(spec)
139169

140170
return `
@@ -159,7 +189,7 @@ ${schemaFields}
159189

160190
function generateFile(specs: ResolvedIntentCommandSpec[]): string {
161191
const fieldDefinitions = specs
162-
.map((spec) => formatFieldDefinitions(spec.fieldSpecs, spec))
192+
.map((spec) => formatFieldDefinitions(spec))
163193
.filter((definition) => definition.length > 0)
164194
const commandDefinitions = specs.map(formatIntentDefinition)
165195
const commandClasses = specs.map(generateClass)
@@ -176,6 +206,7 @@ import {
176206
GeneratedBundledFileIntentCommand,
177207
GeneratedNoInputIntentCommand,
178208
GeneratedStandardFileIntentCommand,
209+
GeneratedWatchableFileIntentCommand,
179210
} from '../intentRuntime.ts'
180211
${fieldDefinitions.join('\n\n')}
181212
${commandDefinitions.join('\n\n')}

packages/node/src/cli/commands/generated-intents.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
GeneratedBundledFileIntentCommand,
2323
GeneratedNoInputIntentCommand,
2424
GeneratedStandardFileIntentCommand,
25+
GeneratedWatchableFileIntentCommand,
2526
} from '../intentRuntime.ts'
2627

2728
const imageGenerateCommandFields = {
@@ -1283,6 +1284,31 @@ const videoThumbsCommandFields = {
12831284
},
12841285
} as const
12851286

1287+
const imageDescribeCommandFields = {
1288+
fields: {
1289+
name: 'fields',
1290+
kind: 'string-array',
1291+
propertyName: 'fields',
1292+
optionFlags: '--fields',
1293+
description:
1294+
'Describe output fields to generate, for example labels or altText,title,caption,description',
1295+
},
1296+
forProfile: {
1297+
name: 'forProfile',
1298+
kind: 'string',
1299+
propertyName: 'forProfile',
1300+
optionFlags: '--for',
1301+
description: 'Use a named output profile, currently: wordpress',
1302+
},
1303+
model: {
1304+
name: 'model',
1305+
kind: 'string',
1306+
propertyName: 'model',
1307+
optionFlags: '--model',
1308+
description: 'Model to use for generated text fields (default: anthropic/claude-sonnet-4-5)',
1309+
},
1310+
} as const
1311+
12861312
const fileCompressCommandFields = {
12871313
format: {
12881314
name: 'format',
@@ -1579,6 +1605,20 @@ const videoEncodeHlsCommandDefinition = {
15791605
},
15801606
} as const
15811607

1608+
const imageDescribeCommandDefinition = {
1609+
commandLabel: 'image describe',
1610+
inputPolicy: {
1611+
kind: 'required',
1612+
},
1613+
outputDescription: 'Write the JSON result to this path or directory',
1614+
execution: {
1615+
kind: 'dynamic-step',
1616+
handler: 'image-describe',
1617+
fields: Object.values(imageDescribeCommandFields),
1618+
resultStepName: 'describe',
1619+
},
1620+
} as const
1621+
15821622
const fileCompressCommandDefinition = {
15831623
commandLabel: 'file compress',
15841624
inputPolicy: {
@@ -2133,6 +2173,39 @@ class VideoEncodeHlsCommand extends GeneratedStandardFileIntentCommand {
21332173
})
21342174
}
21352175

2176+
class ImageDescribeCommand extends GeneratedWatchableFileIntentCommand {
2177+
static override paths = [['image', 'describe']]
2178+
2179+
static override intentDefinition = imageDescribeCommandDefinition
2180+
2181+
static override usage = Command.Usage({
2182+
category: 'Intent Commands',
2183+
description: 'Describe images as labels or publishable text fields',
2184+
details:
2185+
'Generates image labels through `/image/describe`, or structured altText/title/caption/description through `/ai/chat`, then writes the JSON result to `--out`.',
2186+
examples: [
2187+
[
2188+
'Describe an image as labels',
2189+
'transloadit image describe --input hero.jpg --out labels.json',
2190+
],
2191+
[
2192+
'Generate WordPress-ready fields',
2193+
'transloadit image describe --input hero.jpg --for wordpress --out fields.json',
2194+
],
2195+
[
2196+
'Request a custom field set',
2197+
'transloadit image describe --input hero.jpg --fields altText,title,caption --out fields.json',
2198+
],
2199+
],
2200+
})
2201+
2202+
fields = createIntentOption(imageDescribeCommandFields.fields)
2203+
2204+
forProfile = createIntentOption(imageDescribeCommandFields.forProfile)
2205+
2206+
model = createIntentOption(imageDescribeCommandFields.model)
2207+
}
2208+
21362209
class FileCompressCommand extends GeneratedBundledFileIntentCommand {
21372210
static override paths = [['file', 'compress']]
21382211

@@ -2187,6 +2260,7 @@ export const intentCommands = [
21872260
TextSpeakCommand,
21882261
VideoThumbsCommand,
21892262
VideoEncodeHlsCommand,
2263+
ImageDescribeCommand,
21902264
FileCompressCommand,
21912265
FileDecompressCommand,
21922266
] as const

0 commit comments

Comments
 (0)