gencow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@gencow/server-bundle",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "index.js"
6
+ }
@@ -0,0 +1,91 @@
1
+ # Gencow Backend — AI Chat App
2
+
3
+ > `gencow add` 명령으로 컴포넌트 추가 시 이 파일이 자동 업데이트됩니다.
4
+
5
+ ## 스키마
6
+
7
+ ### conversations
8
+ | 컬럼 | 타입 | 설명 |
9
+ |------|------|------|
10
+ | id | serial | PK |
11
+ | title | text | 대화 제목 |
12
+ | userId | text | 사용자 ID |
13
+ | createdAt | timestamp | 생성일 |
14
+ | updatedAt | timestamp | 수정일 |
15
+
16
+ ### messages
17
+ | 컬럼 | 타입 | 설명 |
18
+ |------|------|------|
19
+ | id | serial | PK |
20
+ | conversationId | serial | 대화 FK |
21
+ | role | text | user / assistant / system |
22
+ | content | text | 메시지 내용 |
23
+ | createdAt | timestamp | 생성일 |
24
+
25
+ ## API
26
+
27
+ ### chat.ts
28
+ | 타입 | 이름 | 설명 |
29
+ |------|------|------|
30
+ | query | `chat.listConversations` | 대화 목록 (최신순) |
31
+ | query | `chat.getMessages` | 대화의 메시지 목록 |
32
+ | mutation | `chat.send` | 메시지 전송 + AI 응답 |
33
+
34
+ ### ai.ts
35
+ | 메서드 | 설명 |
36
+ |--------|------|
37
+ | `ai.chat()` | 텍스트 생성 |
38
+ | `ai.stream()` | 스트리밍 응답 |
39
+ | `ai.embed()` | 텍스트 임베딩 |
40
+
41
+ > `.env`에 `OPENAI_API_KEY` 설정 필요
42
+
43
+ ## 인증 (better-auth)
44
+
45
+ Gencow는 **better-auth** (세션 + 쿠키 기반) 인증을 사용합니다.
46
+
47
+ | 엔드포인트 | 메서드 | 설명 |
48
+ |------------|--------|------|
49
+ | `/api/auth/sign-up/email` | POST | 회원가입 (`{ email, password, name }`) |
50
+ | `/api/auth/sign-in/email` | POST | 로그인 (`{ email, password }`) |
51
+ | `/api/auth/sign-out` | POST | 로그아웃 |
52
+
53
+ 프론트엔드에서는 `@gencow/react`의 `gencowAuth()` 사용:
54
+
55
+ ```ts
56
+ import { gencowAuth } from "@gencow/react";
57
+ const { signIn, signUp, signOut, useAuth } = gencowAuth();
58
+ ```
59
+
60
+ > ⚠️ JWT 토큰이나 `/auth/register` 같은 커스텀 경로를 만들지 마세요.
61
+
62
+ ## 📡 REST API 직접 호출
63
+
64
+ > 아래 REST 경로로 API를 호출하세요.
65
+ > 모든 요청에 `credentials: "include"` (쿠키)를 포함해야 합니다.
66
+
67
+ ```ts
68
+ // Query (GET)
69
+ const res = await fetch("http://localhost:5456/api/fn/tasks/list", {
70
+ credentials: "include",
71
+ });
72
+
73
+ // Mutation (POST)
74
+ const res = await fetch("http://localhost:5456/api/fn/tasks/create", {
75
+ method: "POST",
76
+ headers: { "Content-Type": "application/json" },
77
+ credentials: "include",
78
+ body: JSON.stringify({ title: "새 태스크" }),
79
+ });
80
+
81
+ // 파일 업로드 (FormData)
82
+ const formData = new FormData();
83
+ formData.append("file", file);
84
+ fetch("http://localhost:5456/api/fn/files/upload", {
85
+ method: "POST",
86
+ credentials: "include",
87
+ body: formData,
88
+ });
89
+ ```
90
+
91
+ > `GET /api/fn` 으로 등록된 전체 API 목록을 JSON으로 조회할 수 있습니다.
@@ -0,0 +1,108 @@
1
+ import { generateText, streamText, embed, generateObject } from "ai";
2
+ import { openai } from "@ai-sdk/openai";
3
+
4
+ // ─── 기본 모델 (자유롭게 변경 가능) ─────────────────────
5
+ const defaultModel = openai("gpt-4o-mini");
6
+
7
+ /**
8
+ * Gencow AI Engine — Vercel AI SDK 래퍼
9
+ *
10
+ * 이 파일을 수정하여 모델, 시스템 프롬프트 등을 커스터마이징할 수 있습니다.
11
+ */
12
+ export const ai = {
13
+ /**
14
+ * 텍스트 생성 (비스트리밍)
15
+ *
16
+ * @example
17
+ * const reply = await ai.chat({
18
+ * system: "You are a helpful assistant.",
19
+ * messages: [{ role: "user", content: "안녕?" }],
20
+ * });
21
+ */
22
+ async chat(options: {
23
+ model?: Parameters<typeof generateText>[0]["model"];
24
+ system?: string;
25
+ messages: { role: string; content: string }[];
26
+ tools?: Record<string, unknown>;
27
+ }) {
28
+ const result = await generateText({
29
+ model: options.model ?? defaultModel,
30
+ system: options.system,
31
+ messages: options.messages as Parameters<typeof generateText>[0]["messages"],
32
+ tools: options.tools as any,
33
+ } as any);
34
+
35
+ if (result.text) return result.text;
36
+
37
+ const toolCalls = (result as any).toolCalls || [];
38
+ const toolResults = (result as any).toolResults || [];
39
+
40
+ if (toolCalls.length > 0 && toolResults.length > 0) {
41
+ const toolSummary = toolResults
42
+ .map((tr: any) => {
43
+ const resultData = tr.result ?? tr.output ?? tr;
44
+ return `[도구: ${tr.toolName}] 결과:\n${JSON.stringify(resultData, null, 2)}`;
45
+ })
46
+ .join("\n\n");
47
+
48
+ const followUpMessages = [
49
+ ...options.messages,
50
+ { role: "assistant", content: `도구를 실행하여 다음 결과를 얻었습니다:\n${toolSummary}` },
51
+ { role: "user", content: "위 결과를 바탕으로 자연스럽게 답변해 주세요." },
52
+ ];
53
+
54
+ const { text: followUpText } = await generateText({
55
+ model: options.model ?? defaultModel,
56
+ system: options.system,
57
+ messages: followUpMessages as Parameters<typeof generateText>[0]["messages"],
58
+ } as any);
59
+
60
+ return followUpText || toolSummary;
61
+ }
62
+
63
+ return result.text || "응답을 생성할 수 없습니다.";
64
+ },
65
+
66
+ /**
67
+ * 스트리밍 응답
68
+ */
69
+ async stream(options: {
70
+ model?: Parameters<typeof streamText>[0]["model"];
71
+ system?: string;
72
+ messages: { role: string; content: string }[];
73
+ }) {
74
+ return streamText({
75
+ model: options.model ?? defaultModel,
76
+ system: options.system,
77
+ messages: options.messages as Parameters<typeof streamText>[0]["messages"],
78
+ });
79
+ },
80
+
81
+ /**
82
+ * 텍스트 임베딩 (RAG / Memory 용)
83
+ */
84
+ async embed(text: string, model?: Parameters<typeof embed>[0]["model"]) {
85
+ const { embedding } = await embed({
86
+ model: model ?? openai.embedding("text-embedding-3-small"),
87
+ value: text,
88
+ });
89
+ return embedding;
90
+ },
91
+
92
+ /**
93
+ * 구조화 출력 — Zod 스키마에 맞는 JSON 객체 생성
94
+ */
95
+ async generateObject<T>(options: {
96
+ model?: Parameters<typeof generateObject>[0]["model"];
97
+ schema: Parameters<typeof generateObject>[0]["schema"];
98
+ prompt: string;
99
+ system?: string;
100
+ }) {
101
+ return generateObject({
102
+ model: options.model ?? defaultModel,
103
+ schema: options.schema,
104
+ prompt: options.prompt,
105
+ system: options.system,
106
+ } as Parameters<typeof generateObject>[0]);
107
+ },
108
+ };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * gencow/chat.ts — AI Chat mutations
3
+ */
4
+ import { eq, desc } from "drizzle-orm";
5
+ import { query, mutation, v } from "@gencow/core";
6
+ import { conversations, messages } from "./schema";
7
+ import { ai } from "./ai";
8
+
9
+ // ─── Queries ─────────────────────────────────────────────
10
+
11
+ export const listConversations = query("chat.listConversations", {
12
+ handler: async (ctx) => {
13
+ return await ctx.db.select().from(conversations).orderBy(desc(conversations.updatedAt));
14
+ }
15
+ });
16
+
17
+ export const getMessages = query("chat.getMessages", {
18
+ args: { conversationId: v.number() },
19
+ handler: async (ctx, args) => {
20
+ return await ctx.db
21
+ .select()
22
+ .from(messages)
23
+ .where(eq(messages.conversationId, args.conversationId))
24
+ .orderBy(messages.createdAt);
25
+ }
26
+ });
27
+
28
+ // ─── Mutations ───────────────────────────────────────────
29
+
30
+ export const send = mutation({
31
+ name: "chat.send",
32
+ invalidates: [],
33
+ args: {
34
+ conversationId: v.optional(v.number()),
35
+ message: v.string(),
36
+ },
37
+ handler: async (ctx, args) => {
38
+ let convId = args.conversationId;
39
+
40
+ // 새 대화 자동 생성
41
+ if (!convId) {
42
+ const user = ctx.auth.getUserIdentity();
43
+ const [conv] = await ctx.db
44
+ .insert(conversations)
45
+ .values({
46
+ title: args.message.slice(0, 50),
47
+ userId: user?.id,
48
+ })
49
+ .returning();
50
+ convId = conv.id;
51
+ }
52
+
53
+ // 사용자 메시지 저장
54
+ await ctx.db.insert(messages).values({
55
+ conversationId: convId,
56
+ role: "user",
57
+ content: args.message,
58
+ });
59
+
60
+ // 대화 히스토리 로드
61
+ const history = await ctx.db
62
+ .select()
63
+ .from(messages)
64
+ .where(eq(messages.conversationId, convId))
65
+ .orderBy(messages.createdAt);
66
+
67
+ // AI 응답 생성
68
+ const reply = await ai.chat({
69
+ system: "You are a helpful assistant. 한국어로 답변해주세요.",
70
+ messages: history.map(m => ({ role: m.role, content: m.content })),
71
+ });
72
+
73
+ // AI 메시지 저장
74
+ await ctx.db.insert(messages).values({
75
+ conversationId: convId,
76
+ role: "assistant",
77
+ content: reply,
78
+ });
79
+
80
+ // 대화 시간 업데이트
81
+ await ctx.db
82
+ .update(conversations)
83
+ .set({ updatedAt: new Date() })
84
+ .where(eq(conversations.id, convId));
85
+
86
+ // 실시간 push
87
+ const freshMessages = await ctx.db
88
+ .select()
89
+ .from(messages)
90
+ .where(eq(messages.conversationId, convId))
91
+ .orderBy(messages.createdAt);
92
+ ctx.realtime.emit("chat.getMessages", freshMessages);
93
+
94
+ const freshConvs = await ctx.db.select().from(conversations).orderBy(desc(conversations.updatedAt));
95
+ ctx.realtime.emit("chat.listConversations", freshConvs);
96
+
97
+ return { conversationId: convId, reply };
98
+ }
99
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * gencow/index.ts — Entry point
3
+ */
4
+ import "./chat";
@@ -0,0 +1,159 @@
1
+ # AI Chat App — 프론트엔드 바이브코딩 프롬프트
2
+
3
+ 이 프로젝트는 Gencow AI 채팅 백엔드가 이미 구현되어 있습니다.
4
+ 아래 정보를 참고하여 채팅 프론트엔드를 만들어주세요.
5
+
6
+ ## 이미 구현된 백엔드
7
+
8
+ - `gencow/schema.ts` — DB 테이블 (conversations, messages)
9
+ - `gencow/chat.ts` — 대화 CRUD + AI 응답 생성 (OpenAI)
10
+ - `gencow/ai.ts` — AI Engine (모델 설정)
11
+
12
+ ## 프론트엔드 세팅 가이드
13
+
14
+ ### 의존성 설치
15
+ ```bash
16
+ npm install @gencow/react lucide-react next-themes
17
+ ```
18
+
19
+ ### 프로바이더 설정
20
+
21
+ ```tsx
22
+ // src/components/gencow-provider.tsx
23
+ "use client";
24
+ import { useAuth } from "@/lib/auth";
25
+ import { GencowProvider } from "@gencow/react";
26
+
27
+ const BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:5456";
28
+
29
+ export function GencowClientProvider({ children }) {
30
+ const { token } = useAuth();
31
+ return (
32
+ <GencowProvider baseUrl={BASE_URL} token={token}>
33
+ {children}
34
+ </GencowProvider>
35
+ );
36
+ }
37
+ ```
38
+
39
+ ### 인증 설정 (⚠️ 중요 — 반드시 이 방식을 따르세요)
40
+
41
+ Gencow는 **better-auth (세션 + 쿠키 기반)** 인증 시스템을 사용합니다.
42
+ **JWT 토큰이나 커스텀 /auth/register 경로를 직접 만들지 마세요.**
43
+
44
+ ```tsx
45
+ // src/lib/auth.ts — 이 파일 하나로 인증 완료
46
+ import { gencowAuth } from "@gencow/react";
47
+ export const { signIn, signUp, signOut, useAuth, store } = gencowAuth();
48
+ ```
49
+
50
+ #### 올바른 사용법 (프론트엔드)
51
+
52
+ ```tsx
53
+ // 회원가입
54
+ await signUp("user@example.com", "password123", "홍길동");
55
+
56
+ // 로그인
57
+ await signIn("user@example.com", "password123");
58
+
59
+ // 로그아웃
60
+ await signOut();
61
+
62
+ // 인증 상태 확인 (React hook)
63
+ const { token, user, isAuthenticated } = useAuth();
64
+ ```
65
+
66
+ #### ❌ 절대 하지 마세요
67
+
68
+ ```tsx
69
+ fetch("/auth/register", { ... }) // ❌ 잘못된 경로
70
+ fetch("/api/auth/register", { ... }) // ❌ 잘못된 경로
71
+ localStorage.setItem("token", jwt) // ❌ gencowAuth()가 자동 관리
72
+ // ✅ 올바른 방법: gencowAuth()의 signIn/signUp/signOut만 사용
73
+ ```
74
+
75
+ #### 실제 백엔드 인증 엔드포인트 (참고용)
76
+
77
+ | 엔드포인트 | 메서드 | 설명 |
78
+ |------------|--------|------|
79
+ | `/api/auth/sign-up/email` | POST | 회원가입 |
80
+ | `/api/auth/sign-in/email` | POST | 로그인 |
81
+ | `/api/auth/sign-out` | POST | 로그아웃 |
82
+
83
+ > 직접 호출 불필요 — `gencowAuth()`가 자동 처리합니다.
84
+
85
+ ## 📡 REST API 직접 호출
86
+
87
+ > 아래 REST 경로로 API를 호출하세요.
88
+ > 모든 요청에 `credentials: "include"` (쿠키)를 포함해야 합니다.
89
+
90
+ ### Query 호출 예시
91
+
92
+ ```ts
93
+ const res = await fetch("http://localhost:5456/api/fn/chat/listConversations", {
94
+ credentials: "include",
95
+ });
96
+ ```
97
+
98
+ ### Mutation 호출 예시
99
+
100
+ ```ts
101
+ const res = await fetch("http://localhost:5456/api/fn/chat/send", {
102
+ method: "POST",
103
+ headers: { "Content-Type": "application/json" },
104
+ credentials: "include",
105
+ body: JSON.stringify({ message: "안녕하세요!" }),
106
+ });
107
+ ```
108
+
109
+ > `GET /api/fn` 으로 등록된 전체 API 목록을 JSON으로 조회할 수 있습니다.
110
+
111
+ ### React Hook 사용 시 (권장)
112
+
113
+ ```tsx
114
+ const conversations = useQuery(api.chat.listConversations);
115
+ const [send] = useMutation(api.chat.send);
116
+ await send({ message: "안녕하세요!" });
117
+ ```
118
+
119
+ ## 사용 가능한 API
120
+
121
+ ### 대화
122
+ | 메서드 | 이름 | 인자 | 설명 |
123
+ |--------|------|------|------|
124
+ | query | `chat.listConversations` | 없음 | 대화 목록 (최신순) |
125
+ | query | `chat.getMessages` | `{ conversationId: number }` | 대화의 메시지 목록 |
126
+ | mutation | `chat.send` | `{ message: string, conversationId?: number }` | 메시지 전송 + AI 응답 |
127
+
128
+ ### 인증
129
+ | 함수 | 설명 |
130
+ |------|------|
131
+ | `signUp(email, password, name)` | 회원가입 |
132
+ | `signIn(email, password)` | 로그인 |
133
+ | `signOut()` | 로그아웃 |
134
+ | `useAuth()` | `{ token, user, isAuthenticated }` |
135
+
136
+ ## 데이터 사용 패턴
137
+
138
+ ```tsx
139
+ import { useQuery, useMutation } from "@gencow/react";
140
+ import { api } from "../gencow/api";
141
+
142
+ // 대화 목록
143
+ const conversations = useQuery(api.chat.listConversations);
144
+
145
+ // 특정 대화의 메시지
146
+ const messages = useQuery(api.chat.getMessages, { conversationId: selectedId });
147
+
148
+ // 메시지 전송 (새 대화 자동 생성 또는 기존 대화에 추가)
149
+ const [send, isSending] = useMutation(api.chat.send);
150
+ await send({ message: "안녕하세요!" });
151
+ await send({ conversationId: 1, message: "추가 질문입니다" });
152
+ ```
153
+
154
+ ## 권장 UI 구조
155
+
156
+ - 좌측 사이드바: 대화 목록 + 새 대화 버튼
157
+ - 중앙: 채팅 메시지 영역 (말풍선 UI)
158
+ - 하단: 입력 필드 + 전송 버튼
159
+ - 상단: 로그인/로그아웃 + 다크 모드 토글
@@ -0,0 +1,25 @@
1
+ /**
2
+ * gencow/schema.ts — AI Chat 스키마
3
+ */
4
+ import {
5
+ pgTable,
6
+ serial,
7
+ text,
8
+ timestamp,
9
+ } from "drizzle-orm/pg-core";
10
+
11
+ export const conversations = pgTable("conversations", {
12
+ id: serial("id").primaryKey(),
13
+ title: text("title").notNull(),
14
+ userId: text("user_id"),
15
+ createdAt: timestamp("created_at").defaultNow().notNull(),
16
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
17
+ });
18
+
19
+ export const messages = pgTable("messages", {
20
+ id: serial("id").primaryKey(),
21
+ conversationId: serial("conversation_id").notNull(),
22
+ role: text("role").notNull(), // "user" | "assistant" | "system"
23
+ content: text("content").notNull(),
24
+ createdAt: timestamp("created_at").defaultNow().notNull(),
25
+ });
@@ -0,0 +1,101 @@
1
+ import { generateText, streamText, embed, generateObject } from "ai";
2
+ import { openai } from "@ai-sdk/openai";
3
+
4
+ // ─── 기본 모델 (자유롭게 변경 가능) ─────────────────────
5
+ // 예: anthropic("claude-3-haiku"), openai("gpt-4o") 등
6
+ const defaultModel = openai("gpt-4o-mini");
7
+
8
+ /**
9
+ * Gencow AI Engine — Vercel AI SDK 래퍼
10
+ *
11
+ * 바이브코더가 이 파일을 직접 수정하여 커스터마이징 가능.
12
+ * "ai.ts 수정해서 시스템 프롬프트 바꿔줘" → AI가 바로 수정 가능.
13
+ */
14
+ export const ai = {
15
+ /**
16
+ * 텍스트 생성 (비스트리밍)
17
+ *
18
+ * @example
19
+ * const reply = await ai.chat({
20
+ * system: "You are a helpful assistant.",
21
+ * messages: [{ role: "user", content: "안녕?" }],
22
+ * });
23
+ */
24
+ async chat(options: {
25
+ model?: Parameters<typeof generateText>[0]["model"];
26
+ system?: string;
27
+ messages: { role: string; content: string }[];
28
+ tools?: Record<string, unknown>;
29
+ }) {
30
+ const { text } = await generateText({
31
+ model: options.model ?? defaultModel,
32
+ system: options.system,
33
+ messages: options.messages as Parameters<typeof generateText>[0]["messages"],
34
+ tools: options.tools as Parameters<typeof generateText>[0]["tools"],
35
+ });
36
+ return text;
37
+ },
38
+
39
+ /**
40
+ * 스트리밍 응답
41
+ *
42
+ * @example
43
+ * const stream = await ai.stream({
44
+ * system: "You are a helpful assistant.",
45
+ * messages: [{ role: "user", content: "안녕?" }],
46
+ * });
47
+ * for await (const chunk of stream.textStream) {
48
+ * process.stdout.write(chunk);
49
+ * }
50
+ */
51
+ async stream(options: {
52
+ model?: Parameters<typeof streamText>[0]["model"];
53
+ system?: string;
54
+ messages: { role: string; content: string }[];
55
+ }) {
56
+ return streamText({
57
+ model: options.model ?? defaultModel,
58
+ system: options.system,
59
+ messages: options.messages as Parameters<typeof streamText>[0]["messages"],
60
+ });
61
+ },
62
+
63
+ /**
64
+ * 텍스트 임베딩 (RAG / Memory 용)
65
+ *
66
+ * @example
67
+ * const vector = await ai.embed("검색할 텍스트");
68
+ */
69
+ async embed(text: string, model?: Parameters<typeof embed>[0]["model"]) {
70
+ const { embedding } = await embed({
71
+ model: model ?? openai.embedding("text-embedding-3-small"),
72
+ value: text,
73
+ });
74
+ return embedding;
75
+ },
76
+
77
+ /**
78
+ * 구조화 출력 — Zod 스키마에 맞는 JSON 객체 생성
79
+ *
80
+ * @example
81
+ * import { z } from "zod";
82
+ * const { object } = await ai.generateObject({
83
+ * schema: z.object({ name: z.string(), age: z.number() }),
84
+ * prompt: "홍길동은 25살입니다.",
85
+ * });
86
+ * // object = { name: "홍길동", age: 25 }
87
+ */
88
+ async generateObject<T>(options: {
89
+ model?: Parameters<typeof generateObject>[0]["model"];
90
+ schema: Parameters<typeof generateObject>[0]["schema"];
91
+ prompt: string;
92
+ system?: string;
93
+ }) {
94
+ return generateObject({
95
+ model: options.model ?? defaultModel,
96
+ schema: options.schema,
97
+ prompt: options.prompt,
98
+ system: options.system,
99
+ } as Parameters<typeof generateObject>[0]);
100
+ },
101
+ };