Skip to content

Commit b6137f8

Browse files
authored
Merge pull request #75 from ut-code/auth-drizzle
ログイン機能をdrizzleで再実装
2 parents 72fc0f6 + f577f48 commit b6137f8

28 files changed

+3008
-153
lines changed

README.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,30 @@
33
https://my-code.utcode.net
44

55
## インストール
6+
67
```bash
78
npm ci
89
```
910

10-
ルートディレクトリに .env.local という名前のファイルを作成し、Gemini APIキーを設定してください
11+
## 開発環境
12+
13+
```bash
14+
npx prisma dev
15+
```
16+
を実行し、`t` キーを押して表示される DATABASE_URL をコピー
17+
18+
ルートディレクトリに .env.local という名前のファイルを作成し、以下の内容を記述
1119
```dotenv
12-
API_KEY="XXXXXXXX"
20+
API_KEY=GeminiAPIキー
21+
BETTER_AUTH_URL=http://localhost:3000
22+
DATABASE_URL="postgres://... (prisma devの出力)"
1323
```
1424

15-
## 開発環境
25+
別のターミナルで、
26+
```bash
27+
npx drizzle-kit migrate
28+
```
29+
でデータベースを初期化
1630

1731
```bash
1832
npm run dev
@@ -29,6 +43,18 @@ npm run lint
2943
```
3044
でコードをチェックします。出てくるwarningやerrorはできるだけ直しましょう。
3145

46+
* データベースのスキーマ(./app/schema/hoge.ts)を編集した場合、 `npx drizzle-kit generate` でmigrationファイルを作成し、 `npx drizzle-kit migrate` でデータベースに反映します。
47+
* また、mainにマージする際に本番環境のデータベースにもmigrateをする必要があります
48+
* スキーマのファイルを追加した場合は app/lib/drizzle.ts でimportを追加する必要があります(たぶん)
49+
* `npx prisma dev` で立ち上げたデータベースは `npx prisma dev ls` でデータベース名の確認・ `npx prisma dev rm default` で削除ができるらしい
50+
51+
### 本番環境の場合
52+
53+
上記の環境変数以外に、
54+
* BETTER_AUTH_SECRET に任意の文字列
55+
* GOOGLE_CLIENT_IDとGOOGLE_CLIENT_SECRETにGoogle OAuthのクライアントIDとシークレット https://www.better-auth.com/docs/authentication/google
56+
* GITHUB_CLIENT_IDとGITHUB_CLIENT_SECRETにGitHub OAuthのクライアントIDとシークレット https://www.better-auth.com/docs/authentication/github
57+
3258
## markdown仕様
3359

3460
````

app/[docs_id]/chatForm.tsx

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

33
import { useState, FormEvent, useEffect } from "react";
4-
import { askAI } from "@/app/actions/chatActions";
54
import useSWR from "swr";
65
import {
76
getQuestionExample,
@@ -10,7 +9,8 @@ import {
109
import { getLanguageName } from "../pagesList";
1110
import { DynamicMarkdownSection } from "./pageContent";
1211
import { useEmbedContext } from "../terminal/embedContext";
13-
import { ChatMessage, useChatHistoryContext } from "./chatHistory";
12+
import { useChatHistoryContext } from "./chatHistory";
13+
import { askAI } from "@/actions/chatActions";
1414

1515
interface ChatFormProps {
1616
docs_id: string;
@@ -71,8 +71,6 @@ export function ChatForm({
7171
setIsLoading(true);
7272
setErrorMessage(null); // Clear previous error message
7373

74-
const userMessage: ChatMessage = { sender: "user", text: inputValue };
75-
7674
let userQuestion = inputValue;
7775
if (!userQuestion && exampleData) {
7876
// 質問が空欄なら、質問例を使用
@@ -83,19 +81,19 @@ export function ChatForm({
8381

8482
const result = await askAI({
8583
userQuestion,
84+
docsId: docs_id,
8685
documentContent,
8786
sectionContent,
8887
replOutputs,
8988
files,
9089
execResults,
9190
});
9291

93-
if (result.error) {
92+
if (result.error !== null) {
9493
setErrorMessage(result.error);
9594
console.log(result.error);
9695
} else {
97-
const aiMessage: ChatMessage = { sender: "ai", text: result.response };
98-
const chatId = addChat(result.targetSectionId, [userMessage, aiMessage]);
96+
addChat(result.chat);
9997
// TODO: chatIdが指す対象の回答にフォーカス
10098
setInputValue("");
10199
close();

app/[docs_id]/chatHistory.tsx

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { ChatWithMessages } from "@/lib/chatHistory";
34
import {
45
createContext,
56
ReactNode,
@@ -8,15 +9,10 @@ import {
89
useState,
910
} from "react";
1011

11-
export interface ChatMessage {
12-
sender: "user" | "ai" | "error";
13-
text: string;
14-
}
15-
1612
export interface IChatHistoryContext {
17-
chatHistories: Record<string, Record<string, ChatMessage[]>>;
18-
addChat: (sectionId: string, messages: ChatMessage[]) => string;
19-
updateChat: (sectionId: string, chatId: string, message: ChatMessage) => void;
13+
chatHistories: ChatWithMessages[];
14+
addChat: (chat: ChatWithMessages) => void;
15+
// updateChat: (sectionId: string, chatId: string, message: ChatMessage) => void;
2016
}
2117
const ChatHistoryContext = createContext<IChatHistoryContext | null>(null);
2218
export function useChatHistoryContext() {
@@ -29,65 +25,26 @@ export function useChatHistoryContext() {
2925
return context;
3026
}
3127

32-
export function ChatHistoryProvider({ children }: { children: ReactNode }) {
33-
const [chatHistories, setChatHistories] = useState<
34-
Record<string, Record<string, ChatMessage[]>>
35-
>({});
28+
export function ChatHistoryProvider({
29+
children,
30+
initialChatHistories,
31+
}: {
32+
children: ReactNode;
33+
initialChatHistories: ChatWithMessages[];
34+
}) {
35+
const [chatHistories, setChatHistories] =
36+
useState<ChatWithMessages[]>(initialChatHistories);
3637
useEffect(() => {
37-
// Load chat histories from localStorage on mount
38-
const chatHistories: Record<string, Record<string, ChatMessage[]>> = {};
39-
for (let i = 0; i < localStorage.length; i++) {
40-
const key = localStorage.key(i);
41-
if (key && key.startsWith("chat/") && key.split("/").length === 3) {
42-
const savedHistory = localStorage.getItem(key);
43-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
44-
const [_, sectionId, chatId] = key.split("/");
45-
if (savedHistory) {
46-
if (!chatHistories[sectionId]) {
47-
chatHistories[sectionId] = {};
48-
}
49-
chatHistories[sectionId][chatId] = JSON.parse(savedHistory);
50-
}
51-
}
52-
}
53-
setChatHistories(chatHistories);
54-
}, []);
38+
setChatHistories(initialChatHistories);
39+
}, [initialChatHistories]);
5540

56-
const addChat = (sectionId: string, messages: ChatMessage[]): string => {
57-
const chatId = Date.now().toString();
58-
const newChatHistories = { ...chatHistories };
59-
if (!newChatHistories[sectionId]) {
60-
newChatHistories[sectionId] = {};
61-
}
62-
newChatHistories[sectionId][chatId] = messages;
63-
setChatHistories(newChatHistories);
64-
localStorage.setItem(
65-
`chat/${sectionId}/${chatId}`,
66-
JSON.stringify(messages)
67-
);
68-
return chatId;
69-
};
70-
const updateChat = (
71-
sectionId: string,
72-
chatId: string,
73-
message: ChatMessage
74-
) => {
75-
const newChatHistories = { ...chatHistories };
76-
if (newChatHistories[sectionId] && newChatHistories[sectionId][chatId]) {
77-
newChatHistories[sectionId][chatId] = [
78-
...newChatHistories[sectionId][chatId],
79-
message,
80-
];
81-
setChatHistories(newChatHistories);
82-
localStorage.setItem(
83-
`chat/${sectionId}/${chatId}`,
84-
JSON.stringify(newChatHistories[sectionId][chatId])
85-
);
86-
}
41+
const addChat = (chat: ChatWithMessages) => {
42+
// サーバー側で追加された新しいchatをクライアント側にも反映する
43+
setChatHistories([...chatHistories, chat]);
8744
};
8845

8946
return (
90-
<ChatHistoryContext.Provider value={{ chatHistories, addChat, updateChat }}>
47+
<ChatHistoryContext.Provider value={{ chatHistories, addChat }}>
9148
{children}
9249
</ChatHistoryContext.Provider>
9350
);

app/[docs_id]/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { MarkdownSection, splitMarkdown } from "./splitMarkdown";
66
import pyodideLock from "pyodide/pyodide-lock.json";
77
import { PageContent } from "./pageContent";
88
import { ChatHistoryProvider } from "./chatHistory";
9+
import { getChat } from "@/lib/chatHistory";
910

1011
export default async function Page({
1112
params,
@@ -44,8 +45,10 @@ export default async function Page({
4445

4546
const splitMdContent: MarkdownSection[] = splitMarkdown(mdContent);
4647

48+
const initialChatHistories = await getChat(docs_id);
49+
4750
return (
48-
<ChatHistoryProvider>
51+
<ChatHistoryProvider initialChatHistories={initialChatHistories}>
4952
<PageContent
5053
documentContent={mdContent}
5154
splitMdContent={splitMdContent}

app/[docs_id]/pageContent.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ export function PageContent(props: PageContentProps) {
9494
</div>
9595
<div key={`${index}-chat`}>
9696
{/* 右側に表示するチャット履歴欄 */}
97-
{Object.entries(chatHistories[section.sectionId] ?? {}).map(
98-
([chatId, messages]) => (
97+
{chatHistories.filter((c) => c.sectionId === section.sectionId).map(
98+
({chatId, messages}) => (
9999
<div
100100
key={chatId}
101101
className="max-w-xs mb-2 p-2 text-sm border border-base-content/10 rounded-sm shadow-sm bg-base-100"
@@ -104,20 +104,20 @@ export function PageContent(props: PageContentProps) {
104104
{messages.map((msg, index) => (
105105
<div
106106
key={index}
107-
className={`chat ${msg.sender === "user" ? "chat-end" : "chat-start"}`}
107+
className={`chat ${msg.role === "user" ? "chat-end" : "chat-start"}`}
108108
>
109109
<div
110110
className={clsx(
111111
"chat-bubble p-1!",
112-
msg.sender === "user" &&
112+
msg.role === "user" &&
113113
"bg-primary text-primary-content",
114-
msg.sender === "ai" &&
114+
msg.role === "ai" &&
115115
"bg-secondary-content dark:bg-neutral text-black dark:text-white",
116-
msg.sender === "error" && "chat-bubble-error"
116+
msg.role === "error" && "chat-bubble-error"
117117
)}
118118
style={{ maxWidth: "100%", wordBreak: "break-word" }}
119119
>
120-
<StyledMarkdown content={msg.text} />
120+
<StyledMarkdown content={msg.content} />
121121
</div>
122122
</div>
123123
))}

0 commit comments

Comments
 (0)