MYPING

https://github.com/balckowl/personarize-typing

Next.js

TypeScript

PostgreSQL

TailwindCSS

Xの投稿からタイピングできる

ネッシー

potekichi

推しアイデア

自分なりにカスタマイズされたものでタイピング

作った背景

自分がよく使うワードでタイピング練習したい

推し技術

oRPCというエラーレベルでの型定義ができる技術を採用

プロジェクト詳細

サービス概要

このサービスは自分が投稿したXのポストの文章でタイピングができるゲームとなっています。

サービスの画面

こちらの画面からXのアカウントで認証して画面を始めることができます。また、設定でカスタムプロンプトを入力でき、Xのポストを加工することもできます。 image

実際のタイピング画面です。実際にXの画面でタイピングしてる感じをユーザーに与えるデザインにしました✨ image

最後に結果が見れますよ image

使用技術

  • Next.js
  • oRPC
  • Vercel
  • Drizzle
  • Gemini
  • Supabase
  • Zod

技術的挑戦と無駄遣い

oRPC

oRPCというエラーレベルで型を定義、共有できるライブラリをNext.jsにマウントする形で使いました。まだあまり知られてなく情報が少ないことやこのライブラリを実際のプロダクトに使うのは初めてだったので新しい挑戦となりました。

oRPCなのにREST APIを簡単に公開でき、Open APIも自動生成してくれます。APIをテストしたい際に非常に便利でした。 image

ただ、CSRFに対応するためにデフォルトでoRPCクライアントがPOSTメソッド呼び出すため、キャッシュを使う際に、クライアントの設定を変更しなくちゃいけない点や、そのほかheaders情報を含める際やNext.jsのキャッシュタグを使う際など少し面倒でした。

const link = new RPCLink<ClientContext>({ url: `${env.NEXT_PUBLIC_APP_URL}/rpc`, headers: async () => { return globalThis.$headers ? Object.fromEntries(await globalThis.$headers()) // use this on ssr : {}; // use this on browser }, method: ({ context }) => { if (context?.cache || context?.next) { return "GET"; } return "POST"; }, fetch: async (request, init, { context }) => { const finalHeaders = new Headers((init as RequestInit).headers); if (context?.headers) { context.headers.forEach((value, key) => { finalHeaders.set(key, value); }); } return globalThis.fetch(request, { ...init, headers: finalHeaders, cache: context?.cache, ...(context?.next ? { next: context.next } : {}), }); }, });

Gemini

Geiminiは、ユーザーのXのポストをローマ字に変換したり、ユーザーが入力したカスタムプロンプトを変更することに使いました。

無料枠の広いモデルだと、指定したjsonの形でなかなか返却してくれず苦戦したのですが、configschemaを渡すことで、ほぼ100%jsonで返してくれるようになりました。

const schema = { type: "array", items: { type: "object", properties: { id: { type: "integer" }, base: { type: "string" }, baseToHiragana: { type: "string" }, baseToKangi: { type: "string" }, }, required: ["id", "base", "baseToHiragana", "baseToKangi"], }, }; const response = await ai.models.generateContent({ model: "gemini-1.5-flash" contents: {promptを書く場所} config: { responseMimeType: "application/json", responseSchema: schema, }, });

Twitter API

Twitter APIで、ユーザーがログインしたタイミングでユーザーのポストを一定量取得し、データベースに保存しています。しかしこのAPI、無料枠だと15分に一度しかリクエストを送れずに非常に困りました。

そこで持っていたTwitterアカウント全てでキーを生成し、Too Many Requestが返されたら、別のアカウントにリクエストするようして、データを取得できる確率を向上させました。

それでも運悪く全てのAPIが制限に引っかかっているユーザーにはこちらが用意したお手製のゴミポストがデータベースに挿入されるようになっています。

ネッシー

@y_ya