Skip to content

Commit fb26fdb

Browse files
committed
feat: add image generation driver to puterai module
1 parent 4e3bd18 commit fb26fdb

File tree

4 files changed

+153
-0
lines changed

4 files changed

+153
-0
lines changed

src/backend/src/modules/puterai/AIInterfaceService.js

+32
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,38 @@ class AIInterfaceService extends BaseService {
3838
}
3939
}
4040
});
41+
42+
col_interfaces.set('puter-image-generation', {
43+
description: 'AI Image Generation.',
44+
methods: {
45+
generate: {
46+
description: 'Generate an image from a prompt.',
47+
parameters: {
48+
prompt: { type: 'string' },
49+
},
50+
result_choices: [
51+
{
52+
names: ['image'],
53+
type: {
54+
$: 'stream',
55+
content_type: 'image',
56+
}
57+
},
58+
{
59+
names: ['url'],
60+
type: {
61+
$: 'string:url:web',
62+
content_type: 'image',
63+
}
64+
},
65+
],
66+
result: {
67+
description: 'URL of the generated image.',
68+
type: 'string'
69+
}
70+
}
71+
}
72+
});
4173
}
4274
}
4375

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const BaseService = require("../../services/BaseService");
2+
const { TypedValue } = require("../../services/drivers/meta/Runtime");
3+
const { Context } = require("../../util/context");
4+
5+
class OpenAIImageGenerationService extends BaseService {
6+
static MODULES = {
7+
openai: require('openai'),
8+
}
9+
async _init () {
10+
const sk_key =
11+
this.config?.openai?.secret_key ??
12+
this.global_config.openai?.secret_key;
13+
14+
this.openai = new this.modules.openai.OpenAI({
15+
apiKey: sk_key
16+
});
17+
}
18+
19+
static IMPLEMENTS = {
20+
['puter-image-generation']: {
21+
async generate ({ prompt, test_mode }) {
22+
const url = await this.generate(prompt, {
23+
ratio: this.constructor.RATIO_SQUARE,
24+
});
25+
26+
if ( test_mode ) {
27+
return new TypedValue({
28+
$: 'string:url:web',
29+
content_type: 'image',
30+
}, 'https://puter-sample-data.puter.site/image_example.png');
31+
}
32+
33+
const image = new TypedValue({
34+
$: 'string:url:web',
35+
content_type: 'image'
36+
}, url);
37+
38+
return image;
39+
}
40+
}
41+
};
42+
43+
static RATIO_SQUARE = { w: 1024, h: 1024 };
44+
static RATIO_PORTRAIT = { w: 1024, h: 1792 };
45+
static RATIO_LANDSCAPE = { w: 1792, h: 1024 };
46+
47+
async generate (prompt, {
48+
ratio,
49+
model,
50+
}) {
51+
if ( typeof prompt !== 'string' ) {
52+
throw new Error('`prompt` must be a string');
53+
}
54+
55+
if ( ! ratio || ! this._validate_ratio(ratio) ) {
56+
throw new Error('`ratio` must be a valid ratio');
57+
}
58+
59+
model = model ?? 'dall-e-3';
60+
61+
const user_private_uid = Context.get('actor')?.private_uid ?? 'UNKNOWN';
62+
if ( user_private_uid === 'UNKNOWN' ) {
63+
this.errors.report('chat-completion-service:unknown-user', {
64+
message: 'failed to get a user ID for an OpenAI request',
65+
alarm: true,
66+
trace: true,
67+
});
68+
}
69+
70+
const result =
71+
await this.openai.images.generate({
72+
user: user_private_uid,
73+
prompt,
74+
size: `${ratio.w}x${ratio.h}`,
75+
});
76+
77+
const spending_meta = {
78+
model,
79+
size: `${ratio.w}x${ratio.h}`,
80+
};
81+
82+
const svc_spending = Context.get('services').get('spending');
83+
svc_spending.record_spending('openai', 'image-generation', spending_meta);
84+
85+
const url = result.data?.[0]?.url;
86+
return url;
87+
}
88+
89+
_validate_ratio (ratio) {
90+
return false
91+
|| ratio === this.constructor.RATIO_SQUARE
92+
|| ratio === this.constructor.RATIO_PORTRAIT
93+
|| ratio === this.constructor.RATIO_LANDSCAPE
94+
;
95+
}
96+
}
97+
98+
module.exports = {
99+
OpenAIImageGenerationService,
100+
};

src/backend/src/modules/puterai/PuterAIModule.js

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class PuterAIModule extends AdvancedBase {
1212

1313
const { OpenAICompletionService } = require('./OpenAICompletionService');
1414
services.registerService('openai-completion', OpenAICompletionService);
15+
16+
const { OpenAIImageGenerationService } = require('./OpenAIImageGenerationService');
17+
services.registerService('openai-image-generation', OpenAIImageGenerationService);
1518
}
1619
}
1720

src/backend/src/modules/puterai/doc/requests.md

+18
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,22 @@ await (await fetch("http://api.puter.localhost:4100/drivers/call", {
4141
}),
4242
"method": "POST",
4343
})).json();
44+
```
45+
46+
```javascript
47+
URL.createObjectURL(await (await fetch("http://api.puter.localhost:4100/drivers/call", {
48+
"headers": {
49+
"Content-Type": "application/json",
50+
"Authorization": `Bearer ${puter.authToken}`,
51+
},
52+
"body": JSON.stringify({
53+
interface: 'puter-image-generation',
54+
driver: 'openai-image-generation',
55+
method: 'generate',
56+
args: {
57+
prompt: 'photorealistic teapot made of swiss cheese',
58+
}
59+
}),
60+
"method": "POST",
61+
})).blob());
4462
```

0 commit comments

Comments
 (0)