#11 GPT & CRON Jobs
> CHATGPT API 받기
https://platform.openai.com/settings/organization/data-controls/sharing
해당 키를 .env 파일 OPENAI_API_KEY값에 넣어두자
> SUPABASE DB 확장프로그램 사용해보자
경로 : DATABASE -> extensions
1. pg_net 활성화
2. pg_cron 활성화
그리고 나서, Intergrations 페이지 -> cron에 가면 해당 작업을 할 수 있다.
> chatgpt api 사용하기
*npm install openai 설치
먼저, 외부 api를 사용하기 위해서는(특히 유료) supabase의 api key를 별도 세팅해야 한다. 기본적으로는 위 public key를 사용해도 문제가 없다. (RLS를 하기 떄문에). 하지만 chatgpt 같은 외부 api를 호출하는 경우에는 아래 service role key를 사용 해야 한다.
- supa-client.ts
export const adminClient = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
- mutations.ts
export const insertIdeas = async (
client: SupabaseClient<Database>,
ideas: string[]
) => {
const { error } = await client.from("gpt_ideas").insert(
ideas.map((idea) => ({
idea,
}))
);
if (error) {
throw error;
}
};
ideas를 배열로 받아와서 map함수로 insert해줌
- generate-idea-page.tsx
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
import { insertIdeas } from "../mutations";
import { adminClient } from "~/supa-client";
const openai = new OpenAI();
const IdeaSchema = z.object({
title: z.string(),
description: z.string({
description: "A short description of the idea. 100 characters max.",
}),
problem: z.string(),
solution: z.string(),
category: z.enum([
"tech",
"business",
"health",
"education",
"finance",
"other",
]),
});
const ResponseSchema = z.object({
potato: z.array(IdeaSchema),
});
export const loader = async () => {
const completion = await openai.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "user",
content:
"Give the name and elevator pitch of startup ideas that can be built by small teams.",
},
{
role: "user",
content:
"For example: 'An app that helps you find the best deals on groceries.', or 'A platform to rent a coder per hour.'",
},
{
role: "user",
content: "Give me 10 ideas.",
},
],
response_format: zodResponseFormat(ResponseSchema, "potato"),
});
const descriptions = completion.choices[0].message.parsed?.potato.map(
(idea) => idea.description
);
if (!descriptions) {
return Response.json(
{
error: "No ideas generated",
},
{ status: 400 }
);
}
await insertIdeas(adminClient, descriptions);
return Response.json({
ok: true,
});
};
zod로 chatgpt 결과값의 포맷을 정해줄 수 있다.
> CRON JOB을 해보자
supabase -> intergrations-> cron부분에서 생성하기를 누른 다음
이 때 endpost url 부분은 localhost가 작동이 안된다. 이 때 localhost를 공개해주는 서비스를 이용해야 한다.
방법은
https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
winget install --id Cloudflare.cloudflared 설치 (윈도우용)
-vite.config.ts
server: {
allowedHosts: true,
},
추가
cloudflared tunnel --url http://localhost:5173/ 실행 (dev가 실행 중이어야 함)
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
import { insertIdeas } from "../mutations";
import { adminClient } from "~/supa-client";
import type { Route } from "./+types/generate-idea-page";
const openai = new OpenAI();
const IdeaSchema = z.object({
title: z.string(),
description: z.string({
description: "A short description of the idea. 100 characters max.",
}),
problem: z.string(),
solution: z.string(),
category: z.enum([
"tech",
"business",
"health",
"education",
"finance",
"other",
]),
});
const ResponseSchema = z.object({
potato: z.array(IdeaSchema),
});
export const action = async ({ request }: Route.ActionArgs) => {
if (request.method !== "POST") {
return new Response(null, { status: 404 });
}
const header = request.headers.get("X-POTATO");
if (!header || header !== "X-TOMATO") {
return new Response(null, { status: 404 });
}
const completion = await openai.beta.chat.completions.parse({
model: "gpt-4o-mini",
messages: [
{
role: "user",
content:
"Give the name and elevator pitch of startup ideas that can be built by small teams.",
},
{
role: "user",
content:
"For example: 'An app that helps you find the best deals on groceries.', or 'A platform to rent a coder per hour.'",
},
{
role: "user",
content: "Give me 10 ideas.",
},
],
response_format: zodResponseFormat(ResponseSchema, "potato"),
});
const descriptions = completion.choices[0].message.parsed?.potato.map(
(idea) => idea.description
);
if (!descriptions) {
return Response.json(
{
error: "No ideas generated",
},
{ status: 400 }
);
}
await insertIdeas(adminClient, descriptions);
return Response.json({
ok: true,
});
};