Skip to content

Commit df67ce0

Browse files
start of the workshop (exrcs reset)
1 parent 2808aea commit df67ce0

16 files changed

+56
-685
lines changed

.gitignore

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,3 @@ node_modules
66
.dev.vars
77

88
.wrangler
9-
10-
/test-results/
11-
/playwright-report/
12-
/blob-report/
13-
/playwright/.cache/

app/card-manager/card-manager-kv.ts

Lines changed: 8 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import { convertReadableStreamToUint8Array, isCard } from "./utils";
2-
3-
const IMAGE_KEY_PREFIX = "/image";
4-
const DATA_KEY_PREFIX = "/data";
5-
61
/**
72
* Trading card manager class that wraps KV, and Workers AI. Handles all of our "business" logic
83
*/
@@ -16,22 +11,8 @@ export class CardManagerKV implements CardManager {
1611
async generateCardImage(
1712
card: Pick<Card, "title" | "description">
1813
): Promise<ReadableStream<Uint8Array>> {
19-
// create prompt
20-
const input = {
21-
prompt: [
22-
`Based on the following title and description, generate card artwork for a trading card`,
23-
`title: ${card.title}`,
24-
`description: ${card.description}`,
25-
].join("\n"),
26-
};
27-
28-
// generate image data from aiBinding
29-
const imageData = await this.env.AI.run(
30-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
31-
input
32-
);
33-
34-
return imageData;
14+
// TODO
15+
throw new Error("Unimplemented");
3516
}
3617

3718
/**
@@ -41,47 +22,17 @@ export class CardManagerKV implements CardManager {
4122
async generateAndSaveCard(
4223
card: Pick<Card, "title" | "description">
4324
): Promise<string> {
44-
const key = crypto.randomUUID();
45-
46-
const cardData = await this.generateCardImage(card);
47-
48-
// we don't know the length of the readable stream returned from ai.run(),
49-
// so we need to read it all into a buffer so we can use it in env.KV.put()
50-
const arrayBuffer = await convertReadableStreamToUint8Array(cardData);
51-
52-
await Promise.all([
53-
this.env.KV.put(`${IMAGE_KEY_PREFIX}/${key}`, arrayBuffer),
54-
this.env.KV.put(`${DATA_KEY_PREFIX}/${key}`, JSON.stringify(card)),
55-
]);
56-
57-
return key;
25+
// TODO
26+
throw new Error("Unimplemented");
5827
}
5928

6029
/**
6130
* @param cardId id of the card get
6231
* @returns card info if found, null otherwise
6332
*/
6433
async getCard(cardId: string): Promise<Card | null> {
65-
const partialCard = await this.env.KV.get<Omit<Card, "imageUrl">>(
66-
`${DATA_KEY_PREFIX}/${cardId}`,
67-
"json"
68-
);
69-
70-
if (!partialCard) {
71-
// key not found
72-
return null;
73-
}
74-
75-
const card = {
76-
...partialCard,
77-
imageUrl: `/image/${cardId}`,
78-
};
79-
80-
if (!isCard(card)) {
81-
throw new Error("Invalid card returned from KV");
82-
}
83-
84-
return card;
34+
// TODO
35+
throw new Error("Unimplemented");
8536
}
8637

8738
/**
@@ -91,6 +42,7 @@ export class CardManagerKV implements CardManager {
9142
async getCardImage(
9243
cardId: string
9344
): Promise<ReadableStream<Uint8Array> | null> {
94-
return await this.env.KV.get(`${IMAGE_KEY_PREFIX}/${cardId}`, "stream");
45+
// TODO
46+
throw new Error("Unimplemented");
9547
}
9648
}

app/card-manager/card-manager-r2.ts

Lines changed: 8 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { convertReadableStreamToUint8Array, isCard } from "./utils";
2-
31
/**
42
* Trading card manager class that wraps KV, R2, and Workers AI. Handles all of our "business" logic
53
*/
@@ -13,22 +11,8 @@ export class CardManagerR2 implements CardManager {
1311
async generateCardImage(
1412
card: Pick<Card, "title" | "description">
1513
): Promise<ReadableStream<Uint8Array>> {
16-
// create prompt
17-
const input = {
18-
prompt: [
19-
`Based on the following title and description, generate card artwork for a trading card`,
20-
`title: ${card.title}`,
21-
`description: ${card.description}`,
22-
].join("\n"),
23-
};
24-
25-
// generate image data from aiBinding
26-
const imageData = await this.env.AI.run(
27-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
28-
input
29-
);
30-
31-
return imageData;
14+
// TODO
15+
throw new Error("Unimplemented");
3216
}
3317

3418
/**
@@ -38,47 +22,17 @@ export class CardManagerR2 implements CardManager {
3822
async generateAndSaveCard(
3923
card: Pick<Card, "title" | "description">
4024
): Promise<string> {
41-
const key = crypto.randomUUID();
42-
43-
const cardData = await this.generateCardImage(card);
44-
45-
// we don't know the length of the readable stream returned from ai.run(),
46-
// so we need to read it all into a buffer so we can use it in env.R2.put()
47-
const arrayBuffer = await convertReadableStreamToUint8Array(cardData);
48-
49-
await Promise.all([
50-
this.env.R2.put(key, arrayBuffer),
51-
this.env.KV.put(key, JSON.stringify(card)),
52-
]);
53-
54-
return key;
25+
// TODO
26+
throw new Error("Unimplemented");
5527
}
5628

5729
/**
5830
* @param cardId id of the card get
5931
* @returns card info if found, null otherwise
6032
*/
6133
async getCard(cardId: string): Promise<Card | null> {
62-
const partialCard = await this.env.KV.get<Omit<Card, "imageUrl">>(
63-
cardId,
64-
"json"
65-
);
66-
67-
if (!partialCard) {
68-
// key not found
69-
return null;
70-
}
71-
72-
const card = {
73-
...partialCard,
74-
imageUrl: `/image/${cardId}`,
75-
};
76-
77-
if (!isCard(card)) {
78-
throw new Error("Invalid card returned from KV");
79-
}
80-
81-
return card;
34+
// TODO
35+
throw new Error("Unimplemented");
8236
}
8337

8438
/**
@@ -88,7 +42,7 @@ export class CardManagerR2 implements CardManager {
8842
async getCardImage(
8943
cardId: string
9044
): Promise<ReadableStream<Uint8Array> | null> {
91-
const r2Obj = await this.env.R2.get(cardId);
92-
return r2Obj?.body ?? null;
45+
// TODO
46+
throw new Error("Unimplemented");
9347
}
9448
}

app/card-manager/test/ai.mock.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
class MockAi implements Ai {
2-
constructor(private mockOptions: MockOptions) {}
2+
constructor() {}
33

44
// @ts-expect-error (we only implement a single signature of the `run` here)
55
async run(
66
_model: BaseAiTextToImageModels,
77
_prompt: AiTextToImageInput,
88
_options?: AiOptions
99
): Promise<AiTextToImageOutput> {
10-
if (this.mockOptions.runDuration) {
11-
await new Promise((resolve) =>
12-
setTimeout(resolve, this.mockOptions.runDuration)
13-
);
14-
}
1510
return new Blob([new Uint8Array()]).stream();
1611
}
1712
}
1813

19-
type MockOptions = { runDuration?: number };
20-
21-
export default function (mockOptions: MockOptions = {}): Ai {
22-
return new MockAi(mockOptions) as unknown as Ai;
14+
export default function (): Ai {
15+
return new MockAi() as unknown as Ai;
2316
}
Lines changed: 7 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { afterEach, assert, describe, expect, it, vi } from "vitest";
1+
import { afterEach, describe, it, vi } from "vitest";
22
import { env } from "cloudflare:test";
33
import { CardManagerKV } from "../card-manager-kv";
4-
import { convertReadableStreamToUint8Array } from "../utils";
54

65
afterEach(() => {
76
vi.spyOn(env.AI, "run").mockRestore();
@@ -11,115 +10,26 @@ describe("test CardManagerKV class", () => {
1110
const cardManager = new CardManagerKV(env);
1211

1312
it("generateCardImage()", async () => {
14-
const title = "test title";
15-
const description = "test description";
16-
17-
const arr = new Uint8Array([1, 2, 3, 4]);
18-
const blob = new Blob([arr]);
19-
const stream = blob.stream();
20-
21-
const expectedPrompt = {
22-
prompt: [
23-
`Based on the following title and description, generate card artwork for a trading card`,
24-
`title: ${title}`,
25-
`description: ${description}`,
26-
].join("\n"),
27-
};
28-
const mockAI = vi.spyOn(env.AI, "run").mockResolvedValueOnce(stream);
29-
30-
const cardData = await cardManager.generateCardImage({
31-
title,
32-
description,
33-
});
34-
35-
expect(cardData).toStrictEqual(stream);
36-
37-
expect(mockAI).toHaveBeenCalledWith(
38-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
39-
expectedPrompt
40-
);
41-
expect(mockAI).toHaveBeenCalledOnce();
13+
// TODO
4214
});
4315

4416
it("generateAndSaveCard()", async () => {
45-
const title = "test title";
46-
const description = "test description";
47-
48-
// create a large array that gets buffered into multiple chunks
49-
const imageArray = new Uint8Array([1, 2, 3, 4]);
50-
const imageBlob = new Blob([imageArray]);
51-
const stream = imageBlob.stream();
52-
const expectedPrompt = {
53-
prompt: [
54-
`Based on the following title and description, generate card artwork for a trading card`,
55-
`title: ${title}`,
56-
`description: ${description}`,
57-
].join("\n"),
58-
};
59-
const mockAI = vi.spyOn(env.AI, "run").mockResolvedValueOnce(stream);
60-
61-
const cardId = await cardManager.generateAndSaveCard({
62-
title,
63-
description,
64-
});
65-
66-
expect(mockAI).toHaveBeenCalledWith(
67-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
68-
expectedPrompt
69-
);
70-
expect(mockAI).toHaveBeenCalledOnce();
71-
72-
// validate image in KV
73-
const kvCardImage = await env.KV.get(`/image/${cardId}`, "arrayBuffer");
74-
assert(kvCardImage !== null);
75-
expect(kvCardImage).toStrictEqual(imageArray.buffer);
76-
77-
// and data
78-
const kvCard = (await env.KV.get(`/data/${cardId}`, "json")) as Card;
79-
expect(kvCard.title).toStrictEqual("test title");
80-
expect(kvCard.description).toStrictEqual("test description");
17+
// TODO
8118
});
8219

8320
it("getCard(): null card", async () => {
84-
const nullCard = await cardManager.getCard("nonexistent-key");
85-
86-
expect(nullCard).toBeNull();
21+
// TODO
8722
});
8823

8924
it("getCard(): non-null card", async () => {
90-
const uuid = crypto.randomUUID();
91-
92-
await env.KV.put(
93-
`/data/${uuid}`,
94-
JSON.stringify({
95-
title: "test title",
96-
description: "test description",
97-
})
98-
);
99-
100-
const card = await cardManager.getCard(uuid);
101-
expect(card?.title).toStrictEqual("test title");
102-
expect(card?.description).toStrictEqual("test description");
103-
expect(card?.imageUrl).toMatch(/^\/image\/.*$/);
25+
// TODO
10426
});
10527

10628
it("getCardImage(): null card", async () => {
107-
const nullCard = await cardManager.getCardImage("nonexistent-key");
108-
109-
expect(nullCard).toBeNull();
29+
// TODO
11030
});
11131

11232
it("getCardImage(): non-null card", async () => {
113-
const uuid = crypto.randomUUID();
114-
115-
const imageData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
116-
117-
await env.KV.put(`/image/${uuid}`, imageData);
118-
119-
const cardImage = await cardManager.getCardImage(uuid);
120-
assert(cardImage !== null);
121-
expect(await convertReadableStreamToUint8Array(cardImage)).toStrictEqual(
122-
imageData
123-
);
33+
// TODO
12434
});
12535
});

0 commit comments

Comments
 (0)