Skip to content

Commit

Permalink
add openapi-categorize-endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jamalsoueidan committed Jun 28, 2024
1 parent 1d18888 commit 9ae0afc
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-azure-functions-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: |
pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
npm install
npm run graphql:codegen --if-present
npm run codegen --if-present
npm run build --if-present
popd
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ jobs:
- name: Install Dependencies
run: npm ci
- name: Generate GraphQL types
run: npm run graphql:codegen
run: npm run codegen
- name: Run Tests
run: npm run test
84 changes: 81 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"generate-docs": "npx @redocly/cli build-docs docs/openapi.yaml -o docs/index.html --title 'Booking Api Documentation'",
"generate-ts": "npm run bundle && npx orval --config ./orval.config.js",
"postprocess": "node postprocess.js",
"graphql:codegen": "npx graphql-codegen -- --watch && npm run postprocess"
"codegen": "npx graphql-codegen -- --watch && npm run postprocess"
},
"dependencies": {
"@azure/functions": "^4.3.0",
Expand All @@ -34,6 +34,7 @@
"jsonwebtoken": "^9.0.2",
"module-alias": "^2.2.3",
"mongoose": "^7.6.10",
"openai": "^4.52.1",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
10 changes: 10 additions & 0 deletions src/functions/openai.function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { app } from "@azure/functions";
import "module-alias/register";
import { OpenAIControllerProductCategorize } from "./openai/controllers/product-categorize";

app.http("openaiProductCategorize", {
methods: ["POST"],
authLevel: "anonymous",
route: "openai/products-categorize",
handler: OpenAIControllerProductCategorize,
});
23 changes: 23 additions & 0 deletions src/functions/openai/controllers/product-categorize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { z } from "zod";
import { _ } from "~/library/handler";
import { OpenAIServiceProductCategorize } from "../services/product-categorize";

export type OpenAIControllerProductCategorizeRequest = {
body: z.infer<typeof OpenAIServiceProductCategorizeSchema>;
};

export const OpenAIServiceProductCategorizeSchema = z.object({
title: z.string(),
description: z.string(),
});

export type OpenaiProductCategorizeResponse = Awaited<
ReturnType<typeof OpenAIServiceProductCategorize>
>;

export const OpenAIControllerProductCategorize = _(
({ body }: OpenAIControllerProductCategorizeRequest) => {
const validateBody = OpenAIServiceProductCategorizeSchema.parse(body);
return OpenAIServiceProductCategorize(validateBody);
}
);
94 changes: 94 additions & 0 deletions src/functions/openai/services/product-categorize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import OpenAI from "openai";
import { shopifyAdmin } from "~/library/shopify";
import { CollectionsQuery } from "~/types/admin.generated";

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

export const OpenAIServiceProductCategorize = async ({
title,
description,
}: {
title: string;
description?: string;
}) => {
try {
const { data } = await shopifyAdmin().request(COLLECTIONS);

const collections = data?.collections.nodes;

// Prepare collections data as context
const collectionsContext = JSON.stringify(collections, null, 2);

const content = `
Given the following product title and description, response with the collection titles that this product fits into. The JSON structure should be:
{
"collections": [
{
id: "gid://shopify/Collection/1111",
title: "example",
ruleSet: {
rules: [{
column
condition
}],
},
},
],
}
Where:
- "collections" includes the existing collections that the product fits into based on the given list of collections.
### Existing Collections:
${collectionsContext}
### Product Details:
Product Title: ${title}
Product Description: ${description}
If you think the product fits multiply collections, it's fine, include them all in the response.
`;

const response = await openai.chat.completions.create({
model: "gpt-4o-2024-05-13",
messages: [
{
role: "system",
content,
},
],
max_tokens: 300,
response_format: {
type: "json_object",
},
});

return JSON.parse(response.choices[0].message.content as any)
.collections as CollectionsQuery["collections"]["nodes"];
} catch (error) {
console.error("Error:", error);
}
};

const COLLECTION_FRAGMENT = `#graphql
fragment CollectionFragment on Collection {
id
title
description
ruleSet {
rules {
column
condition
}
}
}
` as const;

export const COLLECTIONS = `#graphql
${COLLECTION_FRAGMENT}
query collections {
collections(first: 250, query: "-Alle AND -Subcategory AND -User") {
nodes {
...CollectionFragment
}
}
}
` as const;
14 changes: 14 additions & 0 deletions src/types/admin.generated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,23 @@ export type DestroyScheduleMetafieldMutationVariables = AdminTypes.Exact<{

export type DestroyScheduleMetafieldMutation = { metafieldDelete?: AdminTypes.Maybe<Pick<AdminTypes.MetafieldDeletePayload, 'deletedId'>> };

export type CollectionFragmentFragment = (
Pick<AdminTypes.Collection, 'id' | 'title' | 'description'>
& { ruleSet?: AdminTypes.Maybe<{ rules: Array<Pick<AdminTypes.CollectionRule, 'column' | 'condition'>> }> }
);

export type CollectionsQueryVariables = AdminTypes.Exact<{ [key: string]: never; }>;


export type CollectionsQuery = { collections: { nodes: Array<(
Pick<AdminTypes.Collection, 'id' | 'title' | 'description'>
& { ruleSet?: AdminTypes.Maybe<{ rules: Array<Pick<AdminTypes.CollectionRule, 'column' | 'condition'>> }> }
)> } };

interface GeneratedQueryTypes {
"#graphql\n query FileGet($id: ID!) {\n node(id: $id) {\n ... on MediaImage {\n preview {\n image {\n url\n width\n height\n }\n }\n }\n }\n }\n": {return: FileGetQuery, variables: FileGetQueryVariables},
"#graphql\n query publications {\n publications(first: 10, catalogType: APP) {\n nodes {\n id\n }\n }\n }\n": {return: PublicationsQuery, variables: PublicationsQueryVariables},
"#graphql\n #graphql\n fragment CollectionFragment on Collection {\n id\n title\n description\n ruleSet {\n rules {\n column\n condition\n }\n }\n }\n\n query collections {\n collections(first: 250, query: \"-Alle AND -Subcategory AND -User\") {\n nodes {\n ...CollectionFragment\n }\n }\n }\n": {return: CollectionsQuery, variables: CollectionsQueryVariables},
}

interface GeneratedMutationTypes {
Expand Down

0 comments on commit 9ae0afc

Please sign in to comment.