diff --git a/.gitignore b/.gitignore index 485c203..7eaedd6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,6 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -/app/generated/prisma + +/public/typescript/ +/app/m-plus-rounded-1c-nohint/ diff --git a/README.md b/README.md index d58b8a2..8fa7273 100644 --- a/README.md +++ b/README.md @@ -3,36 +3,30 @@ https://my-code.utcode.net ## インストール + ```bash npm ci ``` +## 開発環境 + +```bash +npx prisma dev +``` +を実行し、`t` キーを押して表示される DATABASE_URL をコピー + ルートディレクトリに .env.local という名前のファイルを作成し、以下の内容を記述 ```dotenv API_KEY=GeminiAPIキー BETTER_AUTH_URL=http://localhost:3000 +DATABASE_URL="postgres://... (prisma devの出力)" ``` -prismaの開発環境を起動 -(.env にDATABASE_URLが自動的に追加される) +別のターミナルで、 ```bash -npx prisma dev +npx drizzle-kit migrate ``` -別ターミナルで -```bash -npx prisma db push -``` - -### 本番環境の場合 - -上記の環境変数以外に、 -* BETTER_AUTH_SECRET に任意の文字列 -* DATABASE_URL に本番用のPostgreSQLデータベースURL -* GOOGLE_CLIENT_IDとGOOGLE_CLIENT_SECRETにGoogle OAuthのクライアントIDとシークレット https://www.better-auth.com/docs/authentication/google -* GITHUB_CLIENT_IDとGITHUB_CLIENT_SECRETにGitHub OAuthのクライアントIDとシークレット https://www.better-auth.com/docs/authentication/github - - -## 開発環境 +でデータベースを初期化 ```bash npm run dev @@ -49,6 +43,71 @@ npm run lint ``` でコードをチェックします。出てくるwarningやerrorはできるだけ直しましょう。 +* データベースのスキーマ(./app/schema/hoge.ts)を編集した場合、 `npx drizzle-kit generate` でmigrationファイルを作成し、 `npx drizzle-kit migrate` でデータベースに反映します。 + * また、mainにマージする際に本番環境のデータベースにもmigrateをする必要があります +* スキーマのファイルを追加した場合は app/lib/drizzle.ts でimportを追加する必要があります(たぶん) +* `npx prisma dev` で立ち上げたデータベースは `npx prisma dev ls` でデータベース名の確認・ `npx prisma dev rm default` で削除ができるらしい + +### 本番環境の場合 + +上記の環境変数以外に、 +* BETTER_AUTH_SECRET に任意の文字列 +* GOOGLE_CLIENT_IDとGOOGLE_CLIENT_SECRETにGoogle OAuthのクライアントIDとシークレット https://www.better-auth.com/docs/authentication/google +* GITHUB_CLIENT_IDとGITHUB_CLIENT_SECRETにGitHub OAuthのクライアントIDとシークレット https://www.better-auth.com/docs/authentication/github + +## ベースとなるドキュメントの作り方 + +- web版の ~~Gemini2.5Pro~~ Gemini3Pro を用いる。 +- 以下のプロンプトで章立てを考えさせる + > `n`章前後から構成される`言語名`のチュートリアルを書こうと思います。章立てを考えてください。`言語名`以外の言語でのプログラミングはある程度やったことがある人を対象にします。 + > +- nを8, 10, 12, 15 など変えて何回か出力させ、それを統合していい感じの章立てを決める +- 実際にドキュメントを書かせる + > 以下の内容で`言語名`チュートリアルの第`n`章を書いてください。他の言語でのプログラミングは経験がある人を対象にします。 + > タイトルにはレベル1の見出し(#), それ以降の見出しにはレベル2以下(##)を使用してください。 + > canvasは使わずに出力してください。 + > REPLで動作可能なコード例はスクリプトではなくREPLの実行例として書いてください。 + > コード例はREPLの実行例では \`\`\``言語名`-repl 、ソースファイルの場合は \`\`\``言語名`:ファイル名`.拡張子` ではじまるコードブロックで示してください。ファイル名は被らないようにしてください。 + > また、ファイルの場合は \`\`\``言語名`-exec:ファイル名`.拡張子` のコードブロック内に実行結果例を記載してください。 + > また、最後には この章のまとめ セクションと、練習問題を2つほど書いてください。練習問題はこの章で学んだ内容を活用してコードを書かせるものにしてください。 + > + > 全体の構成 + > `1. hoge` + > `2. fuga` + > `3. piyo` + > `4. ...` + > + > `第n章: 第n章のタイトル` + > `第n章内の見出し・内容の概要…` + > +- Gemini出力の調整 + - Canvasを使われた場合はやり直す。(Canvasはファイル名付きコードブロックで壊れる) + - 太字がなぜか `**キーワード**` の代わりに `\*\*キーワード\*\*` となっている場合がある。 `\*\*` → `**` の置き換えで対応 + - 見出しの前に `-----` (水平線)が入る場合がある。my.code();は水平線の表示に対応しているが、消す方向で統一 + - `言語名-repl` にはページ内で一意なIDを追加する (例: `言語名-repl:1`) + - REPLの出力部分に書かれたコメントは消えるので修正する + - ダメな例 + ```` + ```js-repl:1 + > console.log("Hello") + Hello // 文字列を表示する + ``` + ```` + - 以下のようにすればok + ```` + ```js-repl:1 + > console.log("Hello") // 文字列を表示する + Hello + + > // 文字列を表示する + > console.log("Hello") + Hello + ``` + ```` + - 練習問題の見出しは「この章のまとめ」の直下のレベル3見出しで、 `### 練習問題n` または `### 練習問題n: タイトル` とする + - 練習問題のファイル名は不都合がなければ `practice(章番号)_(問題番号).拡張子` で統一。空でもよいのでファイルコードブロックとexecコードブロックを置く +- 1章にはたぶん練習問題要らない。 + ## markdown仕様 ```` diff --git a/app/[docs_id]/chatForm.tsx b/app/[docs_id]/chatForm.tsx index 381e014..fa96b15 100644 --- a/app/[docs_id]/chatForm.tsx +++ b/app/[docs_id]/chatForm.tsx @@ -1,12 +1,12 @@ "use client"; import { useState, FormEvent, useEffect } from "react"; -import useSWR from "swr"; -import { - getQuestionExample, - QuestionExampleParams, -} from "../actions/questionExample"; -import { getLanguageName } from "../pagesList"; +// import useSWR from "swr"; +// import { +// getQuestionExample, +// QuestionExampleParams, +// } from "../actions/questionExample"; +// import { getLanguageName } from "../pagesList"; import { DynamicMarkdownSection } from "./pageContent"; import { useEmbedContext } from "../terminal/embedContext"; import { useChatHistoryContext } from "./chatHistory"; @@ -32,31 +32,31 @@ export function ChatForm({ const { addChat } = useChatHistoryContext(); - const lang = getLanguageName(docs_id); + // const lang = getLanguageName(docs_id); const { files, replOutputs, execResults } = useEmbedContext(); - const documentContentInView = sectionContent - .filter((s) => s.inView) - .map((s) => s.rawContent) - .join("\n\n"); - const { data: exampleData, error: exampleError } = useSWR( - // 質問フォームを開いたときだけで良い - { - lang, - documentContent: documentContentInView, - } satisfies QuestionExampleParams, - getQuestionExample, - { - // リクエストは古くても構わないので1回でいい - revalidateIfStale: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - } - ); - if (exampleError) { - console.error("Error getting question example:", exampleError); - } + // const documentContentInView = sectionContent + // .filter((s) => s.inView) + // .map((s) => s.rawContent) + // .join("\n\n"); + // const { data: exampleData, error: exampleError } = useSWR( + // // 質問フォームを開いたときだけで良い + // { + // lang, + // documentContent: documentContentInView, + // } satisfies QuestionExampleParams, + // getQuestionExample, + // { + // // リクエストは古くても構わないので1回でいい + // revalidateIfStale: false, + // revalidateOnFocus: false, + // revalidateOnReconnect: false, + // } + // ); + // if (exampleError) { + // console.error("Error getting question example:", exampleError); + // } // 質問フォームを開くたびにランダムに選び直し、 // exampleData[Math.floor(exampleChoice * exampleData.length)] を採用する const [exampleChoice, setExampleChoice] = useState(0); // 0〜1 @@ -71,13 +71,13 @@ export function ChatForm({ setIsLoading(true); setErrorMessage(null); // Clear previous error message - let userQuestion = inputValue; - if (!userQuestion && exampleData) { - // 質問が空欄なら、質問例を使用 - userQuestion = - exampleData[Math.floor(exampleChoice * exampleData.length)]; - setInputValue(userQuestion); - } + const userQuestion = inputValue; + // if (!userQuestion && exampleData) { + // // 質問が空欄なら、質問例を使用 + // userQuestion = + // exampleData[Math.floor(exampleChoice * exampleData.length)]; + // setInputValue(userQuestion); + // } const result = await askAI({ userQuestion, @@ -104,7 +104,7 @@ export function ChatForm({ return (