Skip to content

Commit ff35130

Browse files
committed
diffをデータベースに保存
1 parent ac302c9 commit ff35130

6 files changed

Lines changed: 635 additions & 14 deletions

File tree

app/actions/chatActions.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { generateContent } from "./gemini";
55
import { DynamicMarkdownSection } from "../[lang]/[pageId]/pageContent";
66
import { ReplCommand, ReplOutput } from "@my-code/runtime/interface";
7-
import { addChat, ChatWithMessages } from "@/lib/chatHistory";
7+
import { addChat, ChatWithMessages, CreateChatDiff } from "@/lib/chatHistory";
88
import { getPagesList, introSectionId, PagePath, SectionId } from "@/lib/docs";
99

1010
type ChatResult =
@@ -198,18 +198,35 @@ export async function askAI(params: ChatParams): Promise<ChatResult> {
198198
targetSectionId = introSectionId(path);
199199
}
200200
const responseMessage = text.split(/-{3,}/)[1].trim();
201-
const diffMatch = text
201+
const diffRaw: CreateChatDiff[] = [];
202+
for (const m of text
202203
.split(/-{3,}/)[2]
203204
.matchAll(
204205
/<{3,}\s*SEARCH\n([\s\S]*?)\n={3,}\n([\s\S]*?)\n>{3,}\s*REPLACE/g
206+
)) {
207+
const search = m[1];
208+
const replace = m[2];
209+
const targetSection = sectionContent.find((s) =>
210+
s.rawContent.includes(search)
205211
);
206-
const diff: { search: string; replace: string }[] = diffMatch
207-
? Array.from(diffMatch, (m) => ({ search: m[1], replace: m[2] }))
208-
: [];
209-
const newChat = await addChat(path, targetSectionId, [
210-
{ role: "user", content: userQuestion },
211-
{ role: "ai", content: responseMessage },
212-
]);
212+
if (targetSection) {
213+
diffRaw.push({
214+
search,
215+
replace,
216+
sectionId: targetSection.id,
217+
targetMD5: targetSection.md5,
218+
});
219+
}
220+
}
221+
const newChat = await addChat(
222+
path,
223+
targetSectionId,
224+
[
225+
{ role: "user", content: userQuestion },
226+
{ role: "ai", content: responseMessage },
227+
],
228+
diffRaw
229+
);
213230
return {
214231
error: null,
215232
chat: newChat,

app/lib/chatHistory.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { headers } from "next/headers";
44
import { getAuthServer } from "./auth";
55
import { getDrizzle } from "./drizzle";
6-
import { chat, message, section } from "@/schema/chat";
6+
import { chat, diff, message, section } from "@/schema/chat";
77
import { and, asc, eq, exists } from "drizzle-orm";
88
import { Auth } from "better-auth";
99
import { revalidateTag, unstable_cacheLife } from "next/cache";
@@ -15,6 +15,12 @@ export interface CreateChatMessage {
1515
role: "user" | "ai" | "error";
1616
content: string;
1717
}
18+
export interface CreateChatDiff {
19+
search: string;
20+
replace: string;
21+
sectionId: SectionId;
22+
targetMD5: string;
23+
}
1824

1925
// cacheに使うキーで、実際のURLではない
2026
const CACHE_KEY_BASE = "https://my-code.utcode.net/chatHistory";
@@ -57,6 +63,7 @@ export async function addChat(
5763
path: PagePath,
5864
sectionId: SectionId,
5965
messages: CreateChatMessage[],
66+
diffRaw: CreateChatDiff[],
6067
context?: Partial<Context>
6168
) {
6269
const { drizzle, userId } = await initContext(context);
@@ -82,6 +89,13 @@ export async function addChat(
8289
)
8390
.returning();
8491

92+
const chatDiffs = await drizzle.insert(diff).values(
93+
diffRaw.map((d) => ({
94+
chatId: newChat.chatId,
95+
...d,
96+
}))
97+
);
98+
8599
revalidateTag(cacheKeyForPage(path, userId));
86100
if (isCloudflare()) {
87101
const cache = await caches.open("chatHistory");
@@ -98,6 +112,7 @@ export async function addChat(
98112
pagePath: `${path.lang}/${path.page}`,
99113
},
100114
messages: chatMessages,
115+
diff: chatDiffs,
101116
};
102117
}
103118

@@ -119,17 +134,20 @@ export async function getChat(
119134
drizzle
120135
.select()
121136
.from(section)
122-
.where(and(
123-
eq(section.sectionId, chat.sectionId),
124-
eq(section.pagePath, `${path.lang}/${path.page}`),
125-
))
137+
.where(
138+
and(
139+
eq(section.sectionId, chat.sectionId),
140+
eq(section.pagePath, `${path.lang}/${path.page}`)
141+
)
142+
)
126143
)
127144
),
128145
with: {
129146
section: true,
130147
messages: {
131148
orderBy: [asc(message.createdAt)],
132149
},
150+
diff: true,
133151
},
134152
orderBy: [asc(chat.createdAt)],
135153
});

app/schema/chat.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,28 @@ export const message = pgTable("message", {
2121
createdAt: timestamp("createdAt").notNull().defaultNow(),
2222
});
2323

24+
export const diff = pgTable("diff", {
25+
id: uuid("id").primaryKey().defaultRandom(),
26+
chatId: uuid("chatId").notNull(),
27+
search: text("search").notNull(),
28+
replace: text("replace").notNull(),
29+
sectionId: text("sectionId").notNull(),
30+
targetMD5: text("targetMD5").notNull(),
31+
createdAt: timestamp("createdAt").notNull().defaultNow(),
32+
});
33+
2434
export const chatRelations = relations(chat, ({ many, one }) => ({
2535
messages: many(message),
2636
section: one(section, {
2737
fields: [chat.sectionId],
2838
references: [section.sectionId],
2939
}),
40+
diff: many(diff),
3041
}));
3142

3243
export const sectionRelations = relations(chat, ({ many }) => ({
3344
chat: many(chat),
45+
// diff: many(diff),
3446
}));
3547

3648
export const messageRelations = relations(message, ({ one }) => ({
@@ -39,3 +51,14 @@ export const messageRelations = relations(message, ({ one }) => ({
3951
references: [chat.chatId],
4052
}),
4153
}));
54+
55+
export const diffRelations = relations(diff, ({ one }) => ({
56+
// section: one(section, {
57+
// fields: [diff.sectionId],
58+
// references: [section.sectionId],
59+
// }),
60+
chat: one(chat, {
61+
fields: [diff.chatId],
62+
references: [chat.chatId],
63+
}),
64+
}));

drizzle/0004_busy_orphan.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE TABLE "diff" (
2+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3+
"chatId" uuid NOT NULL,
4+
"search" text NOT NULL,
5+
"replace" text NOT NULL,
6+
"sectionId" text NOT NULL,
7+
"targetMD5" text NOT NULL,
8+
"createdAt" timestamp DEFAULT now() NOT NULL
9+
);

0 commit comments

Comments
 (0)