Skip to content

Commit 6ff62d9

Browse files
committed
lib/chatHidtoryからactionとcacheを分離、chatページで実際のチャットを取得
1 parent b3fd3c9 commit 6ff62d9

7 files changed

Lines changed: 95 additions & 45 deletions

File tree

app/(docs)/@chat/chat/[chatId]/page.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ChatAreaStateUpdater } from "@/(docs)/chatAreaState";
2+
import { getChatOne, initContext } from "@/lib/chatHistory";
23
import { StyledMarkdown } from "@/markdown/markdown";
34
import clsx from "clsx";
45
import Link from "next/link";
@@ -10,11 +11,8 @@ export default async function ChatPage({
1011
}) {
1112
const { chatId } = await params;
1213

13-
// TODO: 実際のchatを取得
14-
const messages = [
15-
{ role: "user", content: "a" },
16-
{ role: "ai", content: "b" },
17-
];
14+
const ctx = await initContext();
15+
const chatData = await getChatOne(chatId, ctx);
1816

1917
return (
2018
<aside
@@ -31,7 +29,7 @@ export default async function ChatPage({
3129
<Link className="btn" href="/chat">
3230
閉じる
3331
</Link>
34-
{messages.map((msg, index) => (
32+
{chatData?.messages.map((msg, index) => (
3533
<div
3634
key={index}
3735
className={`chat ${msg.role === "user" ? "chat-end" : "chat-start"}`}

app/(docs)/@docs/[lang]/[pageId]/chatHistory.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

3-
import { ChatWithMessages, getChat } from "@/lib/chatHistory";
3+
import { getChatAllWithoutCache } from "@/actions/getChatAll";
4+
import { ChatWithMessages } from "@/lib/chatHistory";
45
import { PagePath } from "@/lib/docs";
56
import {
67
createContext,
@@ -45,7 +46,7 @@ export function ChatHistoryProvider({
4546
// その後、クライアント側で最新のchatHistoriesを改めて取得して更新する
4647
const { data: fetchedChatHistories } = useSWR<ChatWithMessages[]>(
4748
path,
48-
getChat,
49+
getChatAllWithoutCache,
4950
{
5051
// リクエストは古くても構わないので1回でいい
5152
revalidateIfStale: false,

app/(docs)/@docs/[lang]/[pageId]/page.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@ import { Metadata } from "next";
22
import { notFound } from "next/navigation";
33
import { PageContent } from "./pageContent";
44
import { ChatHistoryProvider } from "./chatHistory";
5-
import { getChatFromCache, initContext } from "@/lib/chatHistory";
5+
import {
6+
cacheKeyForPage,
7+
ChatWithMessages,
8+
getAllChat,
9+
getChatFromCache,
10+
initContext,
11+
} from "@/lib/chatHistory";
612
import {
713
getMarkdownSections,
814
getPagesList,
915
LangId,
16+
PagePath,
1017
PageSlug,
1118
} from "@/lib/docs";
19+
import { unstable_cacheLife, unstable_cacheTag } from "next/cache";
20+
import { isCloudflare } from "@/lib/detectCloudflare";
1221

1322
export async function generateMetadata({
1423
params,
@@ -38,7 +47,8 @@ export default async function Page({
3847
const { lang, pageId } = await params;
3948
const pagesList = await getPagesList();
4049
const langEntry = pagesList.find((l) => l.id === lang);
41-
const pageEntryIndex = langEntry?.pages.findIndex((p) => p.slug === pageId) ?? -1;
50+
const pageEntryIndex =
51+
langEntry?.pages.findIndex((p) => p.slug === pageId) ?? -1;
4252
const pageEntry = langEntry?.pages[pageEntryIndex];
4353
if (!langEntry || !pageEntry) notFound();
4454

@@ -50,7 +60,7 @@ export default async function Page({
5060
const sections = await getMarkdownSections(lang, pageId);
5161

5262
const context = await initContext();
53-
const initialChatHistories = await getChatFromCache(path, context);
63+
const initialChatHistories = await getChatFromCache(path, context.userId);
5464

5565
return (
5666
<ChatHistoryProvider
@@ -68,3 +78,31 @@ export default async function Page({
6878
</ChatHistoryProvider>
6979
);
7080
}
81+
82+
export async function getChatFromCache(path: PagePath, userId?: string) {
83+
// チャットの取得をキャッシュする。
84+
// use cacheの仕様で、drizzleオブジェクトとauthオブジェクトは引数に渡せない。
85+
// 一方、use cacheの関数内でheaders()にはアクセスできない。
86+
// したがって、外でheaders()を使ってuserIdを取得した後、関数の中で再度drizzleを初期化しないといけない。
87+
"use cache";
88+
unstable_cacheLife("days");
89+
90+
if (!userId) {
91+
return [];
92+
}
93+
unstable_cacheTag(cacheKeyForPage(path, userId));
94+
95+
if (isCloudflare()) {
96+
const cache = await caches.open("chatHistory");
97+
const cachedResponse = await cache.match(cacheKeyForPage(path, userId));
98+
if (cachedResponse) {
99+
// console.log("Cache hit for chatHistory/getChat");
100+
const data = (await cachedResponse.json()) as ChatWithMessages[];
101+
return data;
102+
} else {
103+
// console.log("Cache miss for chatHistory/getChat");
104+
}
105+
}
106+
const ctx = await initContext({ userId });
107+
return await getAllChat(path, ctx);
108+
}

app/actions/chatActions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
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, CreateChatDiff } from "@/lib/chatHistory";
7+
import {
8+
addChat,
9+
ChatWithMessages,
10+
CreateChatDiff,
11+
initContext,
12+
} from "@/lib/chatHistory";
813
import { getPagesList, introSectionId, PagePath, SectionId } from "@/lib/docs";
914

1015
type ChatResult =
@@ -235,7 +240,8 @@ export async function askAI(params: ChatParams): Promise<ChatResult> {
235240
{ role: "user", content: userQuestion },
236241
{ role: "ai", content: responseMessage },
237242
],
238-
diffRaw
243+
diffRaw,
244+
await initContext()
239245
);
240246
return {
241247
error: null,

app/actions/getChatAll.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"use server";
2+
3+
import { getAllChat, initContext } from "@/lib/chatHistory";
4+
import { PagePath } from "@/lib/docs";
5+
6+
export async function getChatAllWithoutCache(path: PagePath) {
7+
const ctx = await initContext();
8+
return await getAllChat(path, ctx);
9+
}

app/lib/chatHistory.ts

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
"use server";
2-
31
import { headers } from "next/headers";
42
import { getAuthServer } from "./auth";
53
import { getDrizzle } from "./drizzle";
64
import { chat, diff, message, section } from "@/schema/chat";
75
import { and, asc, eq, exists } from "drizzle-orm";
86
import { Auth } from "better-auth";
9-
import { revalidateTag, unstable_cacheLife } from "next/cache";
7+
import { revalidateTag } from "next/cache";
108
import { isCloudflare } from "./detectCloudflare";
11-
import { unstable_cacheTag } from "next/cache";
129
import { PagePath, SectionId } from "./docs";
1310

1411
export interface CreateChatMessage {
@@ -24,7 +21,7 @@ export interface CreateChatDiff {
2421

2522
// cacheに使うキーで、実際のURLではない
2623
const CACHE_KEY_BASE = "https://my-code.utcode.net/chatHistory";
27-
function cacheKeyForPage(path: PagePath, userId: string) {
24+
export function cacheKeyForPage(path: PagePath, userId: string) {
2825
return `${CACHE_KEY_BASE}/getChat?path=${path.lang}/${path.page}&userId=${userId}`;
2926
}
3027

@@ -34,6 +31,8 @@ interface Context {
3431
userId?: string;
3532
}
3633
/**
34+
* drizzleとbetterAuthをまとめて初期化する関数
35+
*
3736
* drizzleが初期化されてなければ初期化し、
3837
* authが初期化されてなければ初期化し、
3938
* userIdがなければセッションから取得してセットする。
@@ -64,9 +63,9 @@ export async function addChat(
6463
sectionId: SectionId,
6564
messages: CreateChatMessage[],
6665
diffRaw: CreateChatDiff[],
67-
context?: Partial<Context>
66+
context: Context
6867
) {
69-
const { drizzle, userId } = await initContext(context);
68+
const { drizzle, userId } = context;
7069
if (!userId) {
7170
throw new Error("Not authenticated");
7271
}
@@ -121,11 +120,11 @@ export async function addChat(
121120

122121
export type ChatWithMessages = Awaited<ReturnType<typeof addChat>>;
123122

124-
export async function getChat(
123+
export async function getAllChat(
125124
path: PagePath,
126-
context?: Partial<Context>
125+
context: Context
127126
): Promise<ChatWithMessages[]> {
128-
const { drizzle, userId } = await initContext(context);
127+
const { drizzle, userId } = context;
129128
if (!userId) {
130129
return [];
131130
}
@@ -167,32 +166,29 @@ export async function getChat(
167166
// @ts-expect-error なぜかchatsの型にsectionとmessagesが含まれていないことになっているが、正しくwithを指定しているし、console.logしてみるとちゃんと含まれている
168167
return chats;
169168
}
170-
export async function getChatFromCache(path: PagePath, context: Context) {
171-
"use cache";
172-
unstable_cacheLife("days");
173169

174-
// cacheされる関数の中でheader()にはアクセスできない。
175-
// なので外でinitContext()を呼んだものを引数に渡す必要がある。
176-
// しかし、drizzleオブジェクトは外から渡せないのでgetChatの中で改めてinitContext()を呼んでdrizzleだけ再初期化している
177-
// こんな意味不明な仕様になっているのはactionから呼ばれる関数とレンダリング時に呼ばれる関数を1ファイルでまとめて定義し共通化しようとしているせい。あとでなんとかする
178-
const { auth, userId } = context;
170+
export async function getChatOne(chatId: string, context: Context) {
171+
const { drizzle, userId } = context;
179172
if (!userId) {
180-
return [];
173+
throw new Error("Not authenticated");
181174
}
182-
unstable_cacheTag(cacheKeyForPage(path, userId));
183175

184-
if (isCloudflare()) {
185-
const cache = await caches.open("chatHistory");
186-
const cachedResponse = await cache.match(cacheKeyForPage(path, userId));
187-
if (cachedResponse) {
188-
console.log("Cache hit for chatHistory/getChat");
189-
const data = (await cachedResponse.json()) as ChatWithMessages[];
190-
return data;
191-
} else {
192-
console.log("Cache miss for chatHistory/getChat");
193-
}
194-
}
195-
return await getChat(path, { auth, userId });
176+
return (await drizzle.query.chat.findFirst({
177+
where: and(eq(chat.chatId, chatId), eq(chat.userId, userId)),
178+
with: {
179+
section: true,
180+
messages: {
181+
orderBy: [asc(message.createdAt)],
182+
},
183+
diff: {
184+
orderBy: [asc(diff.createdAt)],
185+
},
186+
},
187+
})) as typeof chat.$inferSelect & {
188+
section: typeof section.$inferSelect;
189+
messages: (typeof message.$inferSelect)[];
190+
diff: (typeof diff.$inferSelect)[];
191+
};
196192
}
197193

198194
export async function migrateChatUser(oldUserId: string, newUserId: string) {

app/markdown/codeBlock.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import { EditorComponent } from "@/terminal/editor";
24
import { ExecFile } from "@/terminal/exec";
35
import { ReplTerminal } from "@/terminal/repl";

0 commit comments

Comments
 (0)