Includes FastAPI+Jinja2+HTMX+SQLite implementation with seed categories, plus deployment templates. Co-authored-by: Cursor <cursoragent@cursor.com>
198 lines
6.3 KiB
TypeScript
198 lines
6.3 KiB
TypeScript
import { PrismaClient, PromptModelProvider } from "@prisma/client";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
async function main() {
|
|
// 최소 사용자(시드): 운영 초기에는 OAuth/Email 붙이기 전까지 작성자 null 허용
|
|
|
|
// 카테고리
|
|
const catModels = await prisma.entityCategory.upsert({
|
|
where: { slug: "models" },
|
|
update: {},
|
|
create: { slug: "models", name: "모델", description: "LLM/이미지 모델" },
|
|
});
|
|
|
|
const catWork = await prisma.entityCategory.upsert({
|
|
where: { slug: "work" },
|
|
update: {},
|
|
create: { slug: "work", name: "업무", description: "업무/상황 기반 대상" },
|
|
});
|
|
|
|
// 엔티티
|
|
const entityChatGPT = await prisma.entity.upsert({
|
|
where: { slug: "chatgpt" },
|
|
update: {},
|
|
create: {
|
|
slug: "chatgpt",
|
|
name: "ChatGPT",
|
|
summary: "대화형 LLM 기반 생산성/개발/글쓰기 도구",
|
|
contentMd:
|
|
"## 개요\nChatGPT는 자연어로 지시를 내리면 다양한 결과물을 생성하는 대화형 모델입니다.\n\n## 추천 사용\n- 요약/정리\n- 문서 초안\n- 코드 리뷰/디버깅\n",
|
|
categoryId: catModels.id,
|
|
},
|
|
});
|
|
|
|
const entityInterview = await prisma.entity.upsert({
|
|
where: { slug: "interview" },
|
|
update: {},
|
|
create: {
|
|
slug: "interview",
|
|
name: "면접",
|
|
summary: "면접 준비/질문/답변 구조화",
|
|
contentMd:
|
|
"## 개요\n면접은 직무 역량과 커뮤니케이션을 검증하는 과정입니다.\n\n## 프롬프트 활용\n- 경험 정리(STAR)\n- 예상 질문 대비\n- 모의면접\n",
|
|
categoryId: catWork.id,
|
|
},
|
|
});
|
|
|
|
// 모델
|
|
const modelChatGPT = await prisma.promptModel.upsert({
|
|
where: { slug: "chatgpt" },
|
|
update: {},
|
|
create: {
|
|
slug: "chatgpt",
|
|
provider: PromptModelProvider.openai,
|
|
name: "ChatGPT (OpenAI)",
|
|
description: "OpenAI ChatGPT 계열",
|
|
},
|
|
});
|
|
|
|
const modelClaude = await prisma.promptModel.upsert({
|
|
where: { slug: "claude" },
|
|
update: {},
|
|
create: {
|
|
slug: "claude",
|
|
provider: PromptModelProvider.anthropic,
|
|
name: "Claude (Anthropic)",
|
|
description: "Anthropic Claude 계열",
|
|
},
|
|
});
|
|
|
|
// 태그
|
|
const tagSummary = await prisma.tag.upsert({
|
|
where: { slug: "summary" },
|
|
update: {},
|
|
create: { slug: "summary", name: "요약" },
|
|
});
|
|
const tagDev = await prisma.tag.upsert({
|
|
where: { slug: "dev" },
|
|
update: {},
|
|
create: { slug: "dev", name: "개발" },
|
|
});
|
|
const tagInterview = await prisma.tag.upsert({
|
|
where: { slug: "interview" },
|
|
update: {},
|
|
create: { slug: "interview", name: "면접" },
|
|
});
|
|
|
|
// Prompt 생성 헬퍼(버전 1 + current_version 세팅 + 예시 + 태그)
|
|
async function createPromptWithV1(args: {
|
|
entityId: string;
|
|
title: string;
|
|
descriptionMd?: string;
|
|
modelId?: string;
|
|
promptText: string;
|
|
changelog?: string;
|
|
tags: string[]; // tag ids
|
|
examples?: Array<{ input?: string; output?: string; note?: string }>;
|
|
}) {
|
|
return prisma.$transaction(async (tx) => {
|
|
const prompt = await tx.prompt.create({
|
|
data: {
|
|
entityId: args.entityId,
|
|
title: args.title,
|
|
descriptionMd: args.descriptionMd,
|
|
modelId: args.modelId,
|
|
},
|
|
});
|
|
|
|
const v1 = await tx.promptVersion.create({
|
|
data: {
|
|
promptId: prompt.id,
|
|
versionNo: 1,
|
|
promptText: args.promptText,
|
|
changelog: args.changelog ?? "초기 등록",
|
|
isApproved: true,
|
|
approvedAt: new Date(),
|
|
},
|
|
});
|
|
|
|
await tx.prompt.update({
|
|
where: { id: prompt.id },
|
|
data: { currentVersionId: v1.id },
|
|
});
|
|
|
|
if (args.tags.length) {
|
|
await tx.promptTag.createMany({
|
|
data: args.tags.map((tagId) => ({ promptId: prompt.id, tagId })),
|
|
skipDuplicates: true,
|
|
});
|
|
}
|
|
|
|
if (args.examples?.length) {
|
|
await tx.promptExample.createMany({
|
|
data: args.examples.map((ex) => ({
|
|
promptVersionId: v1.id,
|
|
inputExample: ex.input,
|
|
outputExample: ex.output,
|
|
note: ex.note,
|
|
})),
|
|
});
|
|
}
|
|
|
|
return { promptId: prompt.id, versionId: v1.id };
|
|
});
|
|
}
|
|
|
|
// 샘플 프롬프트
|
|
await createPromptWithV1({
|
|
entityId: entityChatGPT.id,
|
|
title: "긴 글 핵심 요약 + 액션아이템 추출",
|
|
descriptionMd:
|
|
"회의록/기사/긴 문서를 붙여넣고 **핵심 요약 + 액션아이템**을 구조화해서 뽑습니다.",
|
|
modelId: modelChatGPT.id,
|
|
promptText:
|
|
"너는 뛰어난 문서 편집자다.\n\n아래 텍스트를 읽고 다음 형식으로 답해라:\n1) 5줄 요약\n2) 핵심 포인트(불릿 5개)\n3) 액션 아이템(담당/기한 포함, 모르면 '미정')\n4) 리스크/의문점\n\n[텍스트]\n{{TEXT}}\n",
|
|
tags: [tagSummary.id],
|
|
examples: [
|
|
{
|
|
input: "TEXT=오늘 회의에서 A는 3/1까지…",
|
|
output: "1) 5줄 요약…\n2) 핵심 포인트…\n3) 액션 아이템…",
|
|
},
|
|
],
|
|
});
|
|
|
|
await createPromptWithV1({
|
|
entityId: entityChatGPT.id,
|
|
title: "코드 리뷰어(버그/보안/성능 체크리스트)",
|
|
descriptionMd:
|
|
"PR diff를 주면 **버그/보안/성능/가독성** 관점에서 리뷰 코멘트를 작성합니다.",
|
|
modelId: modelClaude.id,
|
|
promptText:
|
|
"너는 시니어 코드 리뷰어다.\n\n다음을 수행해라:\n- 잠재 버그\n- 보안 취약점\n- 성능 병목\n- 코드 스타일/가독성\n- 테스트 제안\n\n출력은 섹션별로 bullet로.\n\n[DIFF]\n{{DIFF}}\n",
|
|
tags: [tagDev.id],
|
|
});
|
|
|
|
await createPromptWithV1({
|
|
entityId: entityInterview.id,
|
|
title: "STAR 기반 경험 정리 + 면접 답변 리라이트",
|
|
descriptionMd:
|
|
"경험을 STAR로 재구성하고, 면접 답변으로 자연스럽게 리라이트합니다.",
|
|
modelId: modelChatGPT.id,
|
|
promptText:
|
|
"너는 면접 코치다.\n\n아래 경험을 STAR(상황/과제/행동/결과)로 정리하고, 60~90초 답변 스크립트로 리라이트해라.\n- 과장 금지, 수치/근거 우선\n- 리스크/실패가 있으면 학습 포인트 포함\n\n[경험]\n{{EXP}}\n",
|
|
tags: [tagInterview.id],
|
|
});
|
|
}
|
|
|
|
main()
|
|
.then(async () => {
|
|
await prisma.$disconnect();
|
|
})
|
|
.catch(async (e) => {
|
|
console.error(e);
|
|
await prisma.$disconnect();
|
|
process.exit(1);
|
|
});
|
|
|