Skip to content

Commit ee9321a

Browse files
start of the workshop (exrcs reset)
1 parent 9843c54 commit ee9321a

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,20 +11,8 @@ export class CardManagerKV implements CardManager {
1611
async generateAndSaveCard(
1712
card: Pick<Card, "title" | "description">
1813
): Promise<string> {
19-
const key = crypto.randomUUID();
20-
21-
const cardData = await this.generateCardImage(card);
22-
23-
// we don't know the length of the readable stream returned from ai.run(),
24-
// so we need to read it all into a buffer so we can use it in env.KV.put()
25-
const arrayBuffer = await convertReadableStreamToUint8Array(cardData);
26-
27-
await Promise.all([
28-
this.env.KV.put(`${IMAGE_KEY_PREFIX}/${key}`, arrayBuffer),
29-
this.env.KV.put(`${DATA_KEY_PREFIX}/${key}`, JSON.stringify(card)),
30-
]);
31-
32-
return key;
14+
// TODO
15+
throw new Error("Unimplemented");
3316
}
3417

3518
/**
@@ -39,49 +22,17 @@ export class CardManagerKV implements CardManager {
3922
async generateCardImage(
4023
card: Pick<Card, "title" | "description">
4124
): Promise<ReadableStream<Uint8Array>> {
42-
// create prompt
43-
const input = {
44-
prompt: [
45-
`Based on the following title and description, generate card artwork for a trading card`,
46-
`title: ${card.title}`,
47-
`description: ${card.description}`,
48-
].join("\n"),
49-
};
50-
51-
// generate image data from aiBinding
52-
const imageData = await this.env.AI.run(
53-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
54-
input
55-
);
56-
57-
return imageData;
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
*/
@@ -9,70 +7,26 @@ export class CardManagerR2 implements CardManager {
97
async generateAndSaveCard(
108
card: Pick<Card, "title" | "description">
119
): Promise<string> {
12-
const key = crypto.randomUUID();
13-
14-
const cardData = await this.generateCardImage(card);
15-
16-
// we don't know the length of the readable stream returned from ai.run(),
17-
// so we need to read it all into a buffer so we can use it in env.R2.put()
18-
const arrayBuffer = await convertReadableStreamToUint8Array(cardData);
19-
20-
await Promise.all([
21-
this.env.R2.put(key, arrayBuffer),
22-
this.env.KV.put(key, JSON.stringify(card)),
23-
]);
24-
25-
return key;
10+
// TODO
11+
throw new Error("Unimplemented");
2612
}
2713

2814
async generateCardImage(
2915
card: Pick<Card, "title" | "description">
3016
): Promise<ReadableStream<Uint8Array>> {
31-
// create prompt
32-
const input = {
33-
prompt: [
34-
`Based on the following title and description, generate card artwork for a trading card`,
35-
`title: ${card.title}`,
36-
`description: ${card.description}`,
37-
].join("\n"),
38-
};
39-
40-
// generate image data from aiBinding
41-
const imageData = await this.env.AI.run(
42-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
43-
input
44-
);
45-
46-
return imageData;
17+
// TODO
18+
throw new Error("Unimplemented");
4719
}
4820

4921
async getCard(cardId: string): Promise<Card | null> {
50-
const partialCard = await this.env.KV.get<Omit<Card, "imageUrl">>(
51-
cardId,
52-
"json"
53-
);
54-
55-
if (!partialCard) {
56-
// key not found
57-
return null;
58-
}
59-
60-
const card = {
61-
...partialCard,
62-
imageUrl: `/image/${cardId}`,
63-
};
64-
65-
if (!isCard(card)) {
66-
throw new Error("Invalid card returned from KV");
67-
}
68-
69-
return card;
22+
// TODO
23+
throw new Error("Unimplemented");
7024
}
7125

7226
async getCardImage(
7327
cardId: string
7428
): Promise<ReadableStream<Uint8Array> | null> {
75-
const r2Obj = await this.env.R2.get(cardId);
76-
return r2Obj?.body ?? null;
29+
// TODO
30+
throw new Error("Unimplemented");
7731
}
7832
}

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("generateAndSaveCard()", async () => {
14-
const title = "test title";
15-
const description = "test description";
16-
17-
// create a large array that gets buffered into multiple chunks
18-
const imageArray = new Uint8Array([1, 2, 3, 4]);
19-
const imageBlob = new Blob([imageArray]);
20-
const stream = imageBlob.stream();
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 cardId = await cardManager.generateAndSaveCard({
31-
title,
32-
description,
33-
});
34-
35-
expect(mockAI).toHaveBeenCalledWith(
36-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
37-
expectedPrompt
38-
);
39-
expect(mockAI).toHaveBeenCalledOnce();
40-
41-
// validate image in KV
42-
const kvCardImage = await env.KV.get(`/image/${cardId}`, "arrayBuffer");
43-
assert(kvCardImage !== null);
44-
expect(kvCardImage).toStrictEqual(imageArray.buffer);
45-
46-
// and data
47-
const kvCard = (await env.KV.get(`/data/${cardId}`, "json")) as Card;
48-
expect(kvCard.title).toStrictEqual("test title");
49-
expect(kvCard.description).toStrictEqual("test description");
13+
// TODO
5014
});
5115

5216
it("generateCardImage()", async () => {
53-
const title = "test title";
54-
const description = "test description";
55-
56-
const arr = new Uint8Array([1, 2, 3, 4]);
57-
const blob = new Blob([arr]);
58-
const stream = blob.stream();
59-
60-
const expectedPrompt = {
61-
prompt: [
62-
`Based on the following title and description, generate card artwork for a trading card`,
63-
`title: ${title}`,
64-
`description: ${description}`,
65-
].join("\n"),
66-
};
67-
const mockAI = vi.spyOn(env.AI, "run").mockResolvedValueOnce(stream);
68-
69-
const cardData = await cardManager.generateCardImage({
70-
title,
71-
description,
72-
});
73-
74-
expect(cardData).toStrictEqual(stream);
75-
76-
expect(mockAI).toHaveBeenCalledWith(
77-
"@cf/stabilityai/stable-diffusion-xl-base-1.0",
78-
expectedPrompt
79-
);
80-
expect(mockAI).toHaveBeenCalledOnce();
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)